sanwo 0.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 +7 -0
- data/README.md +99 -0
- data/lib/sanwo/client.rb +315 -0
- data/lib/sanwo/providers.rb +35 -0
- data/lib/sanwo/rails/engine.rb +17 -0
- data/lib/sanwo/rails/helper.rb +50 -0
- data/lib/sanwo/version.rb +5 -0
- data/lib/sanwo.rb +9 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2372fbf0134b38a2c9714b59a26767a1cae3f2464866cc0a5f5bf5069716285a
|
|
4
|
+
data.tar.gz: df0a25fbfac2b94d6d5db1177c44e6b8e7501c0d71fd3ddf224df38dfc55a1b0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 1f4c77a24307b071a8b1158ece4ded02a608a477f25e25d21b70d9c7167fdb9758255876919729484447e33599d0a0b2df9958379fc2e4631183872dd96f817f
|
|
7
|
+
data.tar.gz: 39cebb9e678e27f15c14884dacd2fa4cf0ae59f6cd8b8056805debe61d46ba1503652117edd2dcdc4b4266dde78283da348dc15901234ffa395612972901dd68
|
data/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Sanwo Ruby SDK
|
|
2
|
+
|
|
3
|
+
Add [Sanwo](https://sanwohq.com) payments to your Ruby application — Rails or standalone.
|
|
4
|
+
|
|
5
|
+
> **Full documentation at [docs.sanwo.dev](https://docs.sanwo.dev/server/ruby/)** — guides, examples, and API reference for every SDK and provider.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
gem install sanwo
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or add to your Gemfile:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
gem "sanwo"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Standalone Usage
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
require "sanwo"
|
|
23
|
+
|
|
24
|
+
client = Sanwo::Client.new(
|
|
25
|
+
provider: "paystack",
|
|
26
|
+
public_key: "pk_test_xxx"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Include the CDN script tag in your <head>
|
|
30
|
+
client.render_script
|
|
31
|
+
# => '<script src="https://cdn.jsdelivr.net/npm/@sanwohq/embed/dist/sanwo.global.js"></script>'
|
|
32
|
+
|
|
33
|
+
# Render a checkout button (amount in minor units)
|
|
34
|
+
client.render_checkout(
|
|
35
|
+
amount: 50000,
|
|
36
|
+
email: "user@example.com",
|
|
37
|
+
description: "Premium plan",
|
|
38
|
+
button_text: "Subscribe"
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Rails Setup
|
|
43
|
+
|
|
44
|
+
Add the gem to your Gemfile and configure an initializer:
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# config/initializers/sanwo.rb
|
|
48
|
+
Rails.application.config.sanwo_client = Sanwo::Client.new(
|
|
49
|
+
provider: "paystack",
|
|
50
|
+
public_key: ENV["PAYSTACK_PUBLIC_KEY"],
|
|
51
|
+
currency: "NGN"
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Then use the view helpers in your templates:
|
|
56
|
+
|
|
57
|
+
```erb
|
|
58
|
+
<head>
|
|
59
|
+
<%= sanwo_scripts %>
|
|
60
|
+
</head>
|
|
61
|
+
<body>
|
|
62
|
+
<%= sanwo_checkout(amount: 50000, email: current_user.email) %>
|
|
63
|
+
</body>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Custom Amount Input
|
|
67
|
+
|
|
68
|
+
```erb
|
|
69
|
+
<%= sanwo_custom_amount(
|
|
70
|
+
email: current_user.email,
|
|
71
|
+
min_amount: 10000,
|
|
72
|
+
max_amount: 1000000,
|
|
73
|
+
placeholder: "Enter donation amount"
|
|
74
|
+
) %>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Custom Provider Template
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
client = Sanwo::Client.new(
|
|
81
|
+
provider: "custom",
|
|
82
|
+
public_key: "pk_xxx",
|
|
83
|
+
template_url: "https://example.com/my-template.html"
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or with an inline template:
|
|
88
|
+
|
|
89
|
+
```ruby
|
|
90
|
+
client = Sanwo::Client.new(
|
|
91
|
+
provider: "custom",
|
|
92
|
+
public_key: "pk_xxx",
|
|
93
|
+
template: "<div>Your custom checkout HTML</div>"
|
|
94
|
+
)
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
Apache 2.0 — see [LICENSE](LICENSE) for details.
|
data/lib/sanwo/client.rb
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
require "json"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
|
|
7
|
+
module Sanwo
|
|
8
|
+
# Server-side HTML generator for Sanwo checkout.
|
|
9
|
+
#
|
|
10
|
+
# This class does NOT make any HTTP requests. It produces front-end markup
|
|
11
|
+
# that delegates to the Sanwo embed script loaded from the CDN.
|
|
12
|
+
class Client
|
|
13
|
+
CDN_URL = "https://cdn.jsdelivr.net/npm/@sanwohq/embed/dist/sanwo.global.js"
|
|
14
|
+
|
|
15
|
+
attr_reader :provider, :public_key, :currency, :debug, :template_url, :template
|
|
16
|
+
|
|
17
|
+
# @param provider [String] payment provider identifier (see {Sanwo::Providers})
|
|
18
|
+
# @param public_key [String] your provider's public / publishable key
|
|
19
|
+
# @param currency [String] ISO 4217 currency code (default: "NGN")
|
|
20
|
+
# @param debug [Boolean] enable browser console logging (default: false)
|
|
21
|
+
# @param template_url [String, nil] URL to fetch a custom template from
|
|
22
|
+
# @param template [String, nil] inline HTML template string
|
|
23
|
+
# @raise [ArgumentError] if public_key is blank or custom provider lacks template
|
|
24
|
+
def initialize(provider:, public_key:, currency: "NGN", debug: false, template_url: nil, template: nil)
|
|
25
|
+
raise ArgumentError, "public_key is required" if public_key.nil? || public_key.to_s.strip.empty?
|
|
26
|
+
|
|
27
|
+
@provider = Providers.resolve(provider)
|
|
28
|
+
@public_key = public_key
|
|
29
|
+
@currency = currency
|
|
30
|
+
@debug = debug
|
|
31
|
+
@template_url = template_url
|
|
32
|
+
@template = template
|
|
33
|
+
|
|
34
|
+
if @provider == Providers::CUSTOM && @template.nil? && @template_url.nil?
|
|
35
|
+
raise ArgumentError, 'Custom provider requires a "template" or "template_url"'
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Return a <script> tag that loads the Sanwo embed script from the CDN.
|
|
40
|
+
#
|
|
41
|
+
# @return [String] HTML script tag
|
|
42
|
+
def render_script
|
|
43
|
+
html = %(<script src="#{h(CDN_URL)}"></script>)
|
|
44
|
+
mark_safe(html)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Render a complete checkout button with inline JavaScript.
|
|
48
|
+
#
|
|
49
|
+
# The returned HTML contains a <button> element and an inline <script>
|
|
50
|
+
# that uses Sanwo.create() and .checkout() to initiate payment on click.
|
|
51
|
+
#
|
|
52
|
+
# All amounts are in minor units (kobo / cents).
|
|
53
|
+
#
|
|
54
|
+
# @param amount [Integer] amount in minor units
|
|
55
|
+
# @param email [String] customer email address
|
|
56
|
+
# @param description [String, nil] human-readable payment description
|
|
57
|
+
# @param reference [String, nil] unique transaction reference (auto-generated if omitted)
|
|
58
|
+
# @param currency [String, nil] override the default currency
|
|
59
|
+
# @param button_text [String] label displayed on the button
|
|
60
|
+
# @param button_class [String] CSS class(es) applied to the button
|
|
61
|
+
# @param first_name [String, nil] customer first name
|
|
62
|
+
# @param last_name [String, nil] customer last name
|
|
63
|
+
# @param phone [String, nil] customer phone number
|
|
64
|
+
# @param callback [String, nil] name of a global JS function to call on completion
|
|
65
|
+
# @return [String] HTML string
|
|
66
|
+
def render_checkout(amount:, email:, description: nil, reference: nil, currency: nil,
|
|
67
|
+
button_text: "Pay Now", button_class: "sanwo-button",
|
|
68
|
+
first_name: nil, last_name: nil, phone: nil, callback: nil)
|
|
69
|
+
validate_checkout(amount, email)
|
|
70
|
+
|
|
71
|
+
ref = reference || "sanwo_#{SecureRandom.hex(8)}"
|
|
72
|
+
cur = currency || @currency
|
|
73
|
+
btn_id = "sanwo-btn-#{SecureRandom.hex(4)}"
|
|
74
|
+
|
|
75
|
+
customer = { "email" => email }
|
|
76
|
+
customer["firstName"] = first_name if first_name
|
|
77
|
+
customer["lastName"] = last_name if last_name
|
|
78
|
+
customer["phone"] = phone if phone
|
|
79
|
+
|
|
80
|
+
checkout_opts = {
|
|
81
|
+
"amount" => amount,
|
|
82
|
+
"currency" => cur,
|
|
83
|
+
"reference" => ref,
|
|
84
|
+
"customer" => customer,
|
|
85
|
+
}
|
|
86
|
+
checkout_opts["description"] = description if description
|
|
87
|
+
|
|
88
|
+
config_json = JSON.generate(build_create_config)
|
|
89
|
+
opts_json = JSON.generate(checkout_opts)
|
|
90
|
+
debug_flag = @debug ? "true" : "false"
|
|
91
|
+
|
|
92
|
+
callback_js = ""
|
|
93
|
+
if callback
|
|
94
|
+
cb = h(callback)
|
|
95
|
+
callback_js = "if (typeof window['#{cb}'] === 'function') { window['#{cb}'](result); }"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
html = <<~HTML
|
|
99
|
+
<button type="button" class="#{h(button_class)}" id="#{btn_id}">#{h(button_text)}</button>
|
|
100
|
+
<script>
|
|
101
|
+
(function() {
|
|
102
|
+
var btn = document.getElementById('#{btn_id}');
|
|
103
|
+
btn.addEventListener('click', function() {
|
|
104
|
+
if (btn.disabled) return;
|
|
105
|
+
btn.disabled = true;
|
|
106
|
+
var sanwo = Sanwo.create(#{config_json});
|
|
107
|
+
if (#{debug_flag}) console.log('[Sanwo] Starting checkout', #{opts_json});
|
|
108
|
+
sanwo.checkout(#{opts_json}).then(function(result) {
|
|
109
|
+
btn.disabled = false;
|
|
110
|
+
if (#{debug_flag}) console.log('[Sanwo] Result', result);
|
|
111
|
+
#{callback_js}
|
|
112
|
+
}).catch(function(err) {
|
|
113
|
+
btn.disabled = false;
|
|
114
|
+
console.error('[Sanwo] Checkout error:', err);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
})();
|
|
118
|
+
</script>
|
|
119
|
+
HTML
|
|
120
|
+
|
|
121
|
+
mark_safe(html)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Render a custom-amount checkout widget.
|
|
125
|
+
#
|
|
126
|
+
# Produces an <input> for the amount (displayed in major units) and a pay
|
|
127
|
+
# button. When submitted, the value is converted to minor units automatically.
|
|
128
|
+
#
|
|
129
|
+
# @param email [String, nil] customer email (if omitted, the widget prompts the user)
|
|
130
|
+
# @param currency [String, nil] override the default currency
|
|
131
|
+
# @param button_text [String] label for the pay button
|
|
132
|
+
# @param placeholder [String] placeholder text for the amount input
|
|
133
|
+
# @param min_amount [Integer, nil] minimum amount in minor units
|
|
134
|
+
# @param max_amount [Integer, nil] maximum amount in minor units
|
|
135
|
+
# @param description [String, nil] human-readable payment description
|
|
136
|
+
# @param first_name [String, nil] customer first name
|
|
137
|
+
# @param last_name [String, nil] customer last name
|
|
138
|
+
# @param phone [String, nil] customer phone number
|
|
139
|
+
# @param callback [String, nil] name of a global JS function to call on completion
|
|
140
|
+
# @return [String] HTML string
|
|
141
|
+
def render_custom_amount(email: nil, currency: nil, button_text: "Pay Now",
|
|
142
|
+
placeholder: "Enter amount", min_amount: nil, max_amount: nil,
|
|
143
|
+
description: nil, first_name: nil, last_name: nil,
|
|
144
|
+
phone: nil, callback: nil)
|
|
145
|
+
cur = currency || @currency
|
|
146
|
+
widget_id = "sanwo-custom-#{SecureRandom.hex(4)}"
|
|
147
|
+
|
|
148
|
+
config_json = JSON.generate(build_create_config)
|
|
149
|
+
debug_flag = @debug ? "true" : "false"
|
|
150
|
+
|
|
151
|
+
# Build data attributes
|
|
152
|
+
attrs = [
|
|
153
|
+
%(data-sanwo-provider="#{h(@provider)}"),
|
|
154
|
+
%(data-sanwo-key="#{h(@public_key)}"),
|
|
155
|
+
%(data-sanwo-currency="#{h(cur)}"),
|
|
156
|
+
%(data-sanwo-custom-amount="true"),
|
|
157
|
+
%(data-sanwo-button-text="#{h(button_text)}"),
|
|
158
|
+
%(data-sanwo-placeholder="#{h(placeholder)}"),
|
|
159
|
+
]
|
|
160
|
+
attrs << %(data-sanwo-debug="true") if @debug
|
|
161
|
+
attrs << %(data-sanwo-template-url="#{h(@template_url)}") if @template_url
|
|
162
|
+
attrs << %(data-sanwo-template="#{h(@template)}") if @template
|
|
163
|
+
attrs << %(data-sanwo-email="#{h(email)}") if email
|
|
164
|
+
attrs << %(data-sanwo-min-amount="#{min_amount}") unless min_amount.nil?
|
|
165
|
+
attrs << %(data-sanwo-max-amount="#{max_amount}") unless max_amount.nil?
|
|
166
|
+
attrs << %(data-sanwo-description="#{h(description)}") if description
|
|
167
|
+
attrs << %(data-sanwo-first-name="#{h(first_name)}") if first_name
|
|
168
|
+
attrs << %(data-sanwo-last-name="#{h(last_name)}") if last_name
|
|
169
|
+
attrs << %(data-sanwo-phone="#{h(phone)}") if phone
|
|
170
|
+
attrs << %(data-sanwo-callback="#{h(callback)}") if callback
|
|
171
|
+
|
|
172
|
+
attrs_str = attrs.join(" ")
|
|
173
|
+
|
|
174
|
+
# Build customer JS object
|
|
175
|
+
customer_fields = []
|
|
176
|
+
customer_fields << "email: #{JSON.generate(email)}" if email
|
|
177
|
+
customer_fields << "firstName: #{JSON.generate(first_name)}" if first_name
|
|
178
|
+
customer_fields << "lastName: #{JSON.generate(last_name)}" if last_name
|
|
179
|
+
customer_fields << "phone: #{JSON.generate(phone)}" if phone
|
|
180
|
+
|
|
181
|
+
customer_js = if customer_fields.any?
|
|
182
|
+
"{ #{customer_fields.join(', ')} }"
|
|
183
|
+
else
|
|
184
|
+
"{ email: emailInput.value }"
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
callback_js = ""
|
|
188
|
+
if callback
|
|
189
|
+
cb = h(callback)
|
|
190
|
+
callback_js = "if (typeof window['#{cb}'] === 'function') { window['#{cb}'](result); }"
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
min_check = ""
|
|
194
|
+
unless min_amount.nil?
|
|
195
|
+
min_major = min_amount / 100.0
|
|
196
|
+
min_check = "if (amountMinor < #{min_amount}) { alert('Minimum amount is #{format('%.2f', min_major)}'); return; }\n "
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
max_check = ""
|
|
200
|
+
unless max_amount.nil?
|
|
201
|
+
max_major = max_amount / 100.0
|
|
202
|
+
max_check = "if (amountMinor > #{max_amount}) { alert('Maximum amount is #{format('%.2f', max_major)}'); return; }\n "
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
email_input_html = ""
|
|
206
|
+
email_input_js = ""
|
|
207
|
+
unless email
|
|
208
|
+
email_input_html = %(\n <input type="email" id="#{widget_id}-email" placeholder="Email address" required style="display:block;margin-bottom:8px;padding:8px;width:100%;box-sizing:border-box;" />)
|
|
209
|
+
email_input_js = "var emailInput = document.getElementById('#{widget_id}-email');\n "
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
description_js = ""
|
|
213
|
+
description_js = " opts.description = #{JSON.generate(description)};\n" if description
|
|
214
|
+
|
|
215
|
+
html = <<~HTML.chomp
|
|
216
|
+
<div id="#{widget_id}" #{attrs_str}>#{email_input_html}
|
|
217
|
+
<input type="number" id="#{widget_id}-amount" placeholder="#{h(placeholder)}" step="0.01" min="0" required style="display:block;margin-bottom:8px;padding:8px;width:100%;box-sizing:border-box;" />
|
|
218
|
+
<button type="button" id="#{widget_id}-btn" class="sanwo-button">#{h(button_text)}</button>
|
|
219
|
+
</div>
|
|
220
|
+
<script>
|
|
221
|
+
(function() {
|
|
222
|
+
var btn = document.getElementById('#{widget_id}-btn');
|
|
223
|
+
var amountInput = document.getElementById('#{widget_id}-amount');
|
|
224
|
+
#{email_input_js} btn.addEventListener('click', function() {
|
|
225
|
+
var amountMajor = parseFloat(amountInput.value);
|
|
226
|
+
if (isNaN(amountMajor) || amountMajor <= 0) { alert('Please enter a valid amount.'); return; }
|
|
227
|
+
var amountMinor = Math.round(amountMajor * 100);
|
|
228
|
+
#{min_check}#{max_check} btn.disabled = true;
|
|
229
|
+
var sanwo = Sanwo.create(#{config_json});
|
|
230
|
+
var opts = {
|
|
231
|
+
amount: amountMinor,
|
|
232
|
+
currency: #{JSON.generate(cur)},
|
|
233
|
+
customer: #{customer_js}
|
|
234
|
+
};
|
|
235
|
+
#{description_js} if (#{debug_flag}) console.log('[Sanwo] Starting checkout', opts);
|
|
236
|
+
sanwo.checkout(opts).then(function(result) {
|
|
237
|
+
btn.disabled = false;
|
|
238
|
+
if (#{debug_flag}) console.log('[Sanwo] Result', result);
|
|
239
|
+
#{callback_js}
|
|
240
|
+
}).catch(function(err) {
|
|
241
|
+
btn.disabled = false;
|
|
242
|
+
console.error('[Sanwo] Checkout error:', err);
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
})();
|
|
246
|
+
</script>
|
|
247
|
+
HTML
|
|
248
|
+
|
|
249
|
+
mark_safe(html)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Return the current configuration as a plain hash.
|
|
253
|
+
#
|
|
254
|
+
# @return [Hash]
|
|
255
|
+
def config
|
|
256
|
+
cfg = {
|
|
257
|
+
provider: @provider,
|
|
258
|
+
public_key: @public_key,
|
|
259
|
+
currency: @currency,
|
|
260
|
+
debug: @debug,
|
|
261
|
+
script_url: CDN_URL,
|
|
262
|
+
}
|
|
263
|
+
cfg[:template_url] = @template_url if @template_url
|
|
264
|
+
cfg[:template] = @template if @template
|
|
265
|
+
cfg
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
private
|
|
269
|
+
|
|
270
|
+
# Build the config hash passed to Sanwo.create() in JavaScript.
|
|
271
|
+
#
|
|
272
|
+
# @return [Hash]
|
|
273
|
+
def build_create_config
|
|
274
|
+
cfg = {
|
|
275
|
+
"provider" => @provider,
|
|
276
|
+
"publicKey" => @public_key,
|
|
277
|
+
}
|
|
278
|
+
cfg["templateUrl"] = @template_url if @template_url
|
|
279
|
+
cfg["template"] = @template if @template
|
|
280
|
+
cfg
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Validate checkout parameters.
|
|
284
|
+
#
|
|
285
|
+
# @raise [ArgumentError] if amount or email are invalid
|
|
286
|
+
def validate_checkout(amount, email)
|
|
287
|
+
unless amount.is_a?(Integer) && amount.positive?
|
|
288
|
+
raise ArgumentError, "amount must be a positive integer (in minor units)"
|
|
289
|
+
end
|
|
290
|
+
if email.nil? || !email.include?("@")
|
|
291
|
+
raise ArgumentError, "A valid email address is required"
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# HTML-escape a string.
|
|
296
|
+
#
|
|
297
|
+
# @param value [String]
|
|
298
|
+
# @return [String]
|
|
299
|
+
def h(value)
|
|
300
|
+
ERB::Util.html_escape(value.to_s)
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# Mark a string as HTML-safe if ActiveSupport is available.
|
|
304
|
+
#
|
|
305
|
+
# @param html [String]
|
|
306
|
+
# @return [String]
|
|
307
|
+
def mark_safe(html)
|
|
308
|
+
if html.respond_to?(:html_safe)
|
|
309
|
+
html.html_safe
|
|
310
|
+
else
|
|
311
|
+
html
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sanwo
|
|
4
|
+
module Providers
|
|
5
|
+
PAYSTACK = "paystack"
|
|
6
|
+
FLUTTERWAVE = "flutterwave"
|
|
7
|
+
RAZORPAY = "razorpay"
|
|
8
|
+
MONNIFY = "monnify"
|
|
9
|
+
INTERSWITCH = "interswitch"
|
|
10
|
+
CUSTOM = "custom"
|
|
11
|
+
|
|
12
|
+
VALID_PROVIDERS = [
|
|
13
|
+
PAYSTACK,
|
|
14
|
+
FLUTTERWAVE,
|
|
15
|
+
RAZORPAY,
|
|
16
|
+
MONNIFY,
|
|
17
|
+
INTERSWITCH,
|
|
18
|
+
CUSTOM,
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
# Resolve and validate a provider string.
|
|
22
|
+
#
|
|
23
|
+
# @param provider [String] the provider identifier
|
|
24
|
+
# @return [String] the downcased, validated provider ID
|
|
25
|
+
# @raise [ArgumentError] if the provider is not recognized
|
|
26
|
+
def self.resolve(provider)
|
|
27
|
+
key = provider.to_s.downcase.strip
|
|
28
|
+
unless VALID_PROVIDERS.include?(key)
|
|
29
|
+
raise ArgumentError, "Unknown provider #{provider.inspect}. Valid providers: #{VALID_PROVIDERS.join(', ')}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
key
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "helper"
|
|
4
|
+
|
|
5
|
+
module Sanwo
|
|
6
|
+
module Rails
|
|
7
|
+
class Engine < ::Rails::Engine
|
|
8
|
+
isolate_namespace Sanwo
|
|
9
|
+
|
|
10
|
+
initializer "sanwo.view_helpers" do
|
|
11
|
+
ActiveSupport.on_load(:action_view) do
|
|
12
|
+
include Sanwo::Rails::Helper
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sanwo
|
|
4
|
+
module Rails
|
|
5
|
+
# View helpers for rendering Sanwo checkout components in Rails templates.
|
|
6
|
+
#
|
|
7
|
+
# The helpers delegate to the {Sanwo::Client} instance stored in
|
|
8
|
+
# +Rails.application.config.sanwo_client+.
|
|
9
|
+
#
|
|
10
|
+
# @example In an initializer (config/initializers/sanwo.rb)
|
|
11
|
+
# Rails.application.config.sanwo_client = Sanwo::Client.new(
|
|
12
|
+
# provider: "paystack",
|
|
13
|
+
# public_key: ENV["PAYSTACK_PUBLIC_KEY"],
|
|
14
|
+
# )
|
|
15
|
+
#
|
|
16
|
+
# @example In a view
|
|
17
|
+
# <%= sanwo_scripts %>
|
|
18
|
+
# <%= sanwo_checkout(amount: 50000, email: "user@example.com") %>
|
|
19
|
+
module Helper
|
|
20
|
+
# Render the <script> tag that loads the Sanwo embed script.
|
|
21
|
+
#
|
|
22
|
+
# @return [String] HTML script tag
|
|
23
|
+
def sanwo_scripts
|
|
24
|
+
sanwo_client.render_script
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Render a checkout button with inline JavaScript.
|
|
28
|
+
#
|
|
29
|
+
# @param opts [Hash] checkout options (see {Sanwo::Client#render_checkout})
|
|
30
|
+
# @return [String] HTML string
|
|
31
|
+
def sanwo_checkout(**opts)
|
|
32
|
+
sanwo_client.render_checkout(**opts)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Render a custom-amount checkout widget.
|
|
36
|
+
#
|
|
37
|
+
# @param opts [Hash] widget options (see {Sanwo::Client#render_custom_amount})
|
|
38
|
+
# @return [String] HTML string
|
|
39
|
+
def sanwo_custom_amount(**opts)
|
|
40
|
+
sanwo_client.render_custom_amount(**opts)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def sanwo_client
|
|
46
|
+
::Rails.application.config.sanwo_client
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/sanwo.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: sanwo
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- SanwoHQ
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-07-30 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: erb
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rails
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '6.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '6.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.0'
|
|
69
|
+
description: Server-side HTML generator for Sanwo checkout — generates HTML/JS that
|
|
70
|
+
loads the Sanwo embed script from CDN and calls Sanwo.create() + .checkout().
|
|
71
|
+
email:
|
|
72
|
+
- hello@sanwohq.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions: []
|
|
75
|
+
extra_rdoc_files: []
|
|
76
|
+
files:
|
|
77
|
+
- README.md
|
|
78
|
+
- lib/sanwo.rb
|
|
79
|
+
- lib/sanwo/client.rb
|
|
80
|
+
- lib/sanwo/providers.rb
|
|
81
|
+
- lib/sanwo/rails/engine.rb
|
|
82
|
+
- lib/sanwo/rails/helper.rb
|
|
83
|
+
- lib/sanwo/version.rb
|
|
84
|
+
homepage: https://sanwohq.com
|
|
85
|
+
licenses:
|
|
86
|
+
- Apache-2.0
|
|
87
|
+
metadata:
|
|
88
|
+
homepage_uri: https://sanwohq.com
|
|
89
|
+
source_code_uri: https://github.com/Sanwohq/ruby
|
|
90
|
+
changelog_uri: https://github.com/Sanwohq/ruby/blob/main/CHANGELOG.md
|
|
91
|
+
post_install_message:
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: '3.0'
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.0.3.1
|
|
107
|
+
signing_key:
|
|
108
|
+
specification_version: 4
|
|
109
|
+
summary: Universal payment SDK for Ruby
|
|
110
|
+
test_files: []
|