paystack 0.1.3 → 0.1.10
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 +5 -5
- data/.gitignore +1 -0
- data/.rspec +2 -2
- data/.travis.yml +16 -0
- data/README.md +475 -20
- data/Rakefile +1 -2
- data/bin/console +0 -0
- data/bin/setup +0 -0
- data/lib/paystack.rb +58 -84
- data/lib/paystack/error.rb +11 -12
- data/lib/paystack/modules/api.rb +14 -7
- data/lib/paystack/objects/balance.rb +12 -0
- data/lib/paystack/objects/banks.rb +12 -0
- data/lib/paystack/objects/base.rb +103 -102
- data/lib/paystack/objects/card.rb +105 -105
- data/lib/paystack/objects/customers.rb +37 -37
- data/lib/paystack/objects/plans.rb +39 -41
- data/lib/paystack/objects/recipients.rb +26 -0
- data/lib/paystack/objects/settlements.rb +12 -0
- data/lib/paystack/objects/subaccounts.rb +37 -0
- data/lib/paystack/objects/subscriptions.rb +40 -0
- data/lib/paystack/objects/transactions.rb +63 -70
- data/lib/paystack/objects/transfers.rb +75 -0
- data/lib/paystack/utils/utils.rb +104 -105
- data/lib/paystack/version.rb +2 -2
- data/paystack.gemspec +8 -6
- metadata +19 -15
- data/key.pem +0 -0
- data/lib/paystack/modules/crypto.rb +0 -11
- data/lib/paystack/modules/tokenmanager.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 83e70ab4ef797bf35ff34e389da52baeb8d5641f7fb1936c48290c8859b56ae4
|
4
|
+
data.tar.gz: 389e8b173266085f3face547aa4754ba65e5b74dd9619ff506312c665fd08707
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 49dcf89974691ac5407e58d3c0e6163c8f8c647f5da37806b73c86256d96dc62a6bd8b32cab672aeaeaf4ecb7c7d0e33a8bc4af2b169c8ef296522ef133017c9
|
7
|
+
data.tar.gz: 86fd1c8c535936134aa9a889fafd43e9fae9af1cc325470a20aeade623a1737f4c1c09add4e421b285e7a71c30ebca94ec81068ac05f4b06f233b068a6f14e74
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
--color
|
2
|
-
--require spec_helper
|
1
|
+
--color
|
2
|
+
--require spec_helper
|
data/.travis.yml
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
sudo: required
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.4.0
|
5
|
+
before_install: gem install bundler
|
6
|
+
cache:
|
7
|
+
directories:
|
8
|
+
- vendor/bundle
|
9
|
+
|
10
|
+
script:
|
11
|
+
- rspec
|
12
|
+
|
13
|
+
deploy:
|
14
|
+
provider: rubygems
|
15
|
+
api_key: "bcc5d3ff98af614e950b21a2df77af20"
|
16
|
+
gem: paystack
|
data/README.md
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
# Paystack
|
2
2
|
|
3
|
+
[](https://travis-ci.org/IkoroVictor/paystack-ruby) [](https://badge.fury.io/rb/paystack)
|
3
4
|
|
4
|
-
A ruby gem for easy integration of Paystack
|
5
|
+
A ruby gem for easy integration of [Paystack](https://paystack.co/).
|
5
6
|
|
6
|
-
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/paystack`. To experiment with that code, run `bin/console` for an interactive prompt.
|
7
|
-
|
8
|
-
TODO: Delete this and the text above, and describe your gem
|
9
7
|
|
10
8
|
## Installation
|
11
9
|
|
@@ -23,43 +21,501 @@ Or install it yourself as:
|
|
23
21
|
|
24
22
|
$ gem install paystack
|
25
23
|
|
26
|
-
## Usage
|
24
|
+
## Basic Usage
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
### Instantiate Paystack Object
|
27
29
|
|
28
|
-
|
30
|
+
```ruby
|
31
|
+
|
32
|
+
paystackObj = Paystack.new(public_key, secret_key)
|
29
33
|
|
30
|
-
|
34
|
+
```
|
31
35
|
|
32
|
-
A
|
36
|
+
A secure way is to set your public and private keys as environmental variables `PAYSTACK_PUBLIC_KEY` and `PAYSTACK_PRIVATE_KEY` respectively. Then you instantiate without parameters
|
33
37
|
|
34
38
|
```ruby
|
35
39
|
|
36
|
-
|
40
|
+
paystackObj = Paystack.new
|
37
41
|
|
38
42
|
```
|
43
|
+
It throws a `PaystackBadKeyError` when either of the keys are invalid or cannot be found as environment variables.
|
44
|
+
|
39
45
|
|
40
|
-
Methods available in the Paystack class include
|
41
46
|
|
47
|
+
### Initialize transaction and get Authorization URL
|
42
48
|
|
49
|
+
```ruby
|
50
|
+
|
51
|
+
transactions = PaystackTransactions.new(paystackObj)
|
52
|
+
result = transactions.initializeTransaction(
|
53
|
+
:reference => "blablablabla-YOUR-UNIQUE-REFERENCE-HERE",
|
54
|
+
:amount => 300000,
|
55
|
+
:email => "xxxxxx@gmail.com",
|
56
|
+
)
|
57
|
+
auth_url = result['data']['authorization_url']
|
58
|
+
```
|
59
|
+
NOTE: Amount is in kobo i.e. `100000 = 100000 kobo = 1000 naira`
|
43
60
|
|
44
|
-
|
61
|
+
|
62
|
+
|
63
|
+
### Charge using Authorization code for returning customers
|
45
64
|
|
46
65
|
```ruby
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
66
|
+
|
67
|
+
result = transactions.chargeAuthorization(
|
68
|
+
"WwdkojpoAJo", # Authorization code
|
69
|
+
"xxxxxx@gmail.com", # Customer email
|
70
|
+
2000000, # Amount
|
71
|
+
:reference => "blablablabla-YOUR-UNIQUE-REFERENCE-HERE"
|
52
72
|
)
|
53
73
|
```
|
54
74
|
|
55
75
|
|
56
76
|
|
77
|
+
## Transactions
|
78
|
+
|
79
|
+
|
80
|
+
|
81
|
+
### List transactions
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
|
85
|
+
page_number = 1
|
86
|
+
transactions = PaystackTransactions.new(paystackObj)
|
87
|
+
result = transactions.list(page_number) #Optional `page_number` parameter
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
### Get a transaction
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
|
95
|
+
transaction_id = "123456778"
|
96
|
+
transactions = PaystackTransactions.new(paystackObj)
|
97
|
+
result = transactions.get(transaction_id)
|
98
|
+
|
99
|
+
```
|
100
|
+
|
101
|
+
### Verify a transaction
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
|
105
|
+
transaction_reference = "blablablabla-YOUR-VALID-UNIQUE-REFERENCE-HERE"
|
106
|
+
transactions = PaystackTransactions.new(paystackObj)
|
107
|
+
result = transactions.verify(transaction_reference)
|
108
|
+
|
109
|
+
```
|
110
|
+
|
111
|
+
|
112
|
+
### Get transaction totals
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
|
116
|
+
transactions = PaystackTransactions.new(paystackObj)
|
117
|
+
result = transactions.totals()
|
118
|
+
|
119
|
+
```
|
120
|
+
|
121
|
+
|
122
|
+
## Customers
|
123
|
+
|
124
|
+
|
125
|
+
### List Customers
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
|
129
|
+
page_number = 1
|
130
|
+
customers = PaystackCustomers.new(paystackObj)
|
131
|
+
result = customers.list(page_number) #Optional `page_number` parameter, 50 items per page
|
132
|
+
customers_list = result['data']
|
133
|
+
|
134
|
+
```
|
135
|
+
|
136
|
+
### Get a customer
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
|
140
|
+
customer_id = "123456778"
|
141
|
+
customers = PaystackCustomers.new(paystackObj)
|
142
|
+
result = customers.get(customer_id)
|
143
|
+
customer = result['data']
|
144
|
+
|
145
|
+
```
|
146
|
+
|
147
|
+
### Create new customer
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
|
151
|
+
customers = PaystackCustomers.new(paystackObj)
|
152
|
+
result = customers.create(
|
153
|
+
:first_name => "Victor",
|
154
|
+
:last_name => "Ikoro",
|
155
|
+
:phone => "+234707666669"
|
156
|
+
:email => "xxxxx@gmail.com"
|
157
|
+
)
|
158
|
+
|
159
|
+
```
|
160
|
+
|
161
|
+
### Update customer details
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
|
165
|
+
customer_id = "123456778"
|
166
|
+
customers = PaystackCustomers.new(paystackObj)
|
167
|
+
# Updating last name and email of customer
|
168
|
+
result = customers.update(
|
169
|
+
customer_id,
|
170
|
+
:last_name => "Ikorodu",
|
171
|
+
:email => "xxxxx-modified@gmail.com"
|
172
|
+
)
|
173
|
+
|
174
|
+
```
|
175
|
+
|
176
|
+
## Plans
|
177
|
+
|
178
|
+
### List Plans
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
|
182
|
+
page_number = 1
|
183
|
+
plans = PaystackPlans.new(paystackObj)
|
184
|
+
result = plans.list(page_number) #Optional `page_number` parameter, 50 items per page
|
185
|
+
plans_list = result['data']
|
186
|
+
|
187
|
+
```
|
188
|
+
|
189
|
+
### Get plan detail
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
|
193
|
+
plan_id = "123456778"
|
194
|
+
plans = PaystackPlans.new(paystackObj)
|
195
|
+
result = plans.get(plan_id)
|
196
|
+
plan = result['data']
|
197
|
+
|
198
|
+
```
|
199
|
+
|
200
|
+
### Create new plan
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
|
204
|
+
plans = PaystackPlans.new(paystackObj)
|
205
|
+
result = plans.create(
|
206
|
+
|
207
|
+
:name => "Test Plan",
|
208
|
+
:description => "Dev Test Plan",
|
209
|
+
:amount => 30000, #in KOBO
|
210
|
+
:interval => "monthly", #monthly, yearly, quarterly, weekly etc
|
211
|
+
:currency => "NGN"
|
212
|
+
)
|
213
|
+
|
214
|
+
```
|
215
|
+
|
216
|
+
### Update plan details
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
|
220
|
+
plan_id = "123456778"
|
221
|
+
plans = PaystackPlans.new(paystackObj)
|
222
|
+
result = plans.update(
|
223
|
+
plan_id,
|
224
|
+
:name => "Test Plan Updated",
|
225
|
+
:amount => 500000, #in KOBO
|
226
|
+
:interval => "weekly"
|
227
|
+
)
|
228
|
+
|
229
|
+
```
|
230
|
+
|
231
|
+
|
232
|
+
## Subscriptions
|
233
|
+
|
234
|
+
### Create new subscription
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
|
238
|
+
subscriptions = PaystackSubscriptions.new(paystackObj)
|
239
|
+
result = subscriptions.create(
|
240
|
+
|
241
|
+
:customer => "customer@email.com",
|
242
|
+
:plan => "123557", #plan id
|
243
|
+
:amount => 30000 #in KOBO
|
244
|
+
)
|
245
|
+
|
246
|
+
```
|
247
|
+
|
248
|
+
### Get subscription detail
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
|
252
|
+
subscription_id = "123456778"
|
253
|
+
subscriptions = PaystackSubscriptions.new(paystackObj)
|
254
|
+
result = subscriptions.get(subscription_id)
|
255
|
+
subscription = result['data']
|
256
|
+
|
257
|
+
```
|
258
|
+
|
259
|
+
### Enable subscription
|
260
|
+
|
261
|
+
```ruby
|
262
|
+
|
263
|
+
subscriptions = PaystackSubscriptions.new(paystackObj)
|
264
|
+
result = subscriptions.enable(
|
265
|
+
:code => "12328833",
|
266
|
+
:token => "EWFWKFJWE" #user email token
|
267
|
+
)
|
268
|
+
|
269
|
+
```
|
270
|
+
|
271
|
+
### Disable subscription
|
272
|
+
|
273
|
+
```ruby
|
274
|
+
|
275
|
+
subscriptions = PaystackSubscriptions.new(paystackObj)
|
276
|
+
result = subscriptions.disable(
|
277
|
+
:code => "12328833",
|
278
|
+
:token => "EWFWKFJWE" #user email token
|
279
|
+
)
|
280
|
+
|
281
|
+
```
|
282
|
+
|
283
|
+
|
284
|
+
## Split Payments
|
285
|
+
|
286
|
+
This Gem is also aware of the API calls that allow you to perform split payments on Paystack. The [Paystack documentation on split payments](https://developers.paystack.co/docs/split-payments-overview) can get you started. Below are some sample calls for [subaccounts](https://developers.paystack.co/docs/create-subaccount) and [banks](https://developers.paystack.co/docs/list-banks).
|
287
|
+
|
288
|
+
## Banks
|
289
|
+
|
290
|
+
### List Banks
|
291
|
+
|
292
|
+
```ruby
|
293
|
+
|
294
|
+
page_number = 1
|
295
|
+
banks = PaystackBanks.new(paystackObj)
|
296
|
+
result = banks.list(page_number) #Optional `page_number` parameter, 50 items per page
|
297
|
+
banks_list = result['data']
|
298
|
+
|
299
|
+
```
|
300
|
+
|
301
|
+
## Subaccounts
|
302
|
+
|
303
|
+
### List Subaccounts
|
304
|
+
|
305
|
+
```ruby
|
306
|
+
|
307
|
+
page_number = 1
|
308
|
+
subaccounts = PaystackSubaccounts.new(paystackObj)
|
309
|
+
result = subaccounts.list(page_number) #Optional `page_number` parameter, 50 items per page
|
310
|
+
subaccounts_list = result['data']
|
311
|
+
|
312
|
+
```
|
313
|
+
|
314
|
+
### Get a subaccount
|
315
|
+
|
316
|
+
```ruby
|
317
|
+
|
318
|
+
subaccount_id = "123456778"
|
319
|
+
subaccounts = PaystackSubaccounts.new(paystackObj)
|
320
|
+
result = subaccounts.get(subaccount_id)
|
321
|
+
subaccount = result['data']
|
322
|
+
|
323
|
+
```
|
324
|
+
|
325
|
+
### Create new subaccount
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
|
329
|
+
subaccounts = PaystackSubaccounts.new(paystackObj)
|
330
|
+
result = subaccounts.create(
|
331
|
+
:business_name => "Madam Ikoro Holdings",
|
332
|
+
:settlement_bank => "Providus Bank",
|
333
|
+
:account_number => "1170766666"
|
334
|
+
:percentage_charge => 3.2
|
335
|
+
)
|
336
|
+
|
337
|
+
```
|
338
|
+
|
339
|
+
### Update subaccount details
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
|
343
|
+
subaccount_id = "123456778"
|
344
|
+
subaccounts = PaystackSubaccounts.new(paystackObj)
|
345
|
+
# Updating primary contact name and email of subaccount
|
346
|
+
result = subaccounts.update(
|
347
|
+
subaccount_id,
|
348
|
+
:primary_contact_name => "Victoria Ikorodu",
|
349
|
+
:primary_contact_email => "xxxxx-modified@gmail.com"
|
350
|
+
)
|
351
|
+
|
352
|
+
```
|
353
|
+
|
354
|
+
## Settlements
|
355
|
+
Fetch settlements made to your bank accounts and the bank accounts for your subaccounts
|
356
|
+
|
357
|
+
### List settlements
|
358
|
+
|
359
|
+
```ruby
|
360
|
+
|
361
|
+
settlements = PaystackSettlements.new(paystackObj)
|
362
|
+
results = settlements.list
|
363
|
+
settlements_list = result['data']
|
364
|
+
|
365
|
+
```
|
366
|
+
|
367
|
+
## Transfers
|
368
|
+
|
369
|
+
The funds transfers feature enables you send money directly from your paystack balance to any Nigerian Bank account. The [Paystack documentation on transfers](https://developers.paystack.co/docs/funds_transfers) can get you started.
|
370
|
+
|
371
|
+
## Balance
|
372
|
+
|
373
|
+
### Check Paystack Balance
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
|
377
|
+
balance = PaystackBalance.new(paystackObj)
|
378
|
+
result = balance.get
|
379
|
+
account_balance = result['data']
|
380
|
+
|
381
|
+
```
|
382
|
+
|
383
|
+
## Transfers
|
384
|
+
|
385
|
+
### Initialize a transfer
|
386
|
+
|
387
|
+
```ruby
|
388
|
+
|
389
|
+
transfer = PaystackTransfers.new(paystackObj)
|
390
|
+
results = transfers.initializeTransfer(
|
391
|
+
:source => "balance", # Must be balance
|
392
|
+
:reason => "Your reason",
|
393
|
+
:amount => 30000, # Amount in kobo
|
394
|
+
:recipient => recipient_code, # Unique recipient code
|
395
|
+
)
|
396
|
+
|
397
|
+
```
|
398
|
+
|
399
|
+
### List transfers
|
400
|
+
|
401
|
+
```ruby
|
402
|
+
|
403
|
+
page_number = 1
|
404
|
+
transactions = PaystackTransfers.new(paystackObj)
|
405
|
+
result = transfers.list(page_number) #Optional `page_number` parameter
|
406
|
+
|
407
|
+
```
|
408
|
+
|
409
|
+
### Get a transfer
|
410
|
+
|
411
|
+
```ruby
|
412
|
+
|
413
|
+
transfer_code = "TRF_uniquecode"
|
414
|
+
transactions = PaystackTransfers.new(paystackObj)
|
415
|
+
result = transactions.get(transaction_code)
|
416
|
+
|
417
|
+
```
|
418
|
+
|
419
|
+
### Finalize a transfer
|
420
|
+
|
421
|
+
```ruby
|
422
|
+
|
423
|
+
transfer = PaystackTransfers.new(paystackObj)
|
424
|
+
results = transfer.authorize(
|
425
|
+
:transfer_code => "TRF_blablabla", # Must be balance
|
426
|
+
:otp => "12350",
|
427
|
+
)
|
428
|
+
|
429
|
+
```
|
430
|
+
|
431
|
+
|
432
|
+
## Transfer Recipients
|
433
|
+
|
434
|
+
### Create new recipient
|
435
|
+
|
436
|
+
```ruby
|
437
|
+
|
438
|
+
recipient = PaystackRecipients.new(paystackObj)
|
439
|
+
result = recipients.create(
|
440
|
+
:type => "nuban", #Must be nuban
|
441
|
+
:name => "Test Plan",
|
442
|
+
:description => "Bla-bla-bla",
|
443
|
+
:account_number => 0123456789, #10 digit account number
|
444
|
+
:bank_code => "044", #monthly, yearly, quarterly, weekly etc
|
445
|
+
:currency => "NGN",
|
446
|
+
|
447
|
+
)
|
448
|
+
|
449
|
+
```
|
450
|
+
|
451
|
+
### List transfer recipients
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
page_number = 1
|
455
|
+
recipients = PaystackRecipients.new(paystackObj)
|
456
|
+
result = recipients.list(page_number) #Optional `page_number` parameter, 50 items per page
|
457
|
+
recipients_list = result['data']
|
458
|
+
|
459
|
+
```
|
460
|
+
|
461
|
+
## Transfer Control
|
462
|
+
|
463
|
+
### Resend OTP
|
464
|
+
|
465
|
+
```ruby
|
466
|
+
transfer_code = "TRF_asdfghjkl" #A unique Transfer code is generated when transfer is created
|
467
|
+
transfer = PaystackTransfers.new(paystackObj)
|
468
|
+
result = transfer.resendOtp(transfer_code)
|
469
|
+
|
470
|
+
|
471
|
+
```
|
472
|
+
|
473
|
+
### Disable OTP for transfers
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
|
477
|
+
transfer = PaystackTransfers.new(paystackObj)
|
478
|
+
result = transfer.disableOtp
|
479
|
+
#OTP is sent to the registered phone number of the account
|
480
|
+
|
481
|
+
```
|
482
|
+
|
483
|
+
### Confirm disabling of OTP for transfers
|
484
|
+
|
485
|
+
```ruby
|
486
|
+
|
487
|
+
otp = "12345"
|
488
|
+
transfer = PaystackTransfers.new(paystackObj)
|
489
|
+
# Updating primary contact name and email of subaccount
|
490
|
+
result = transfer.confirmDisableOtp(
|
491
|
+
:otp => otp, #Must be valid OTP sent to the registered phone number
|
492
|
+
)
|
493
|
+
|
494
|
+
```
|
495
|
+
|
496
|
+
### Enable OTP for transfers
|
497
|
+
|
498
|
+
```ruby
|
499
|
+
|
500
|
+
transfer = PaystackTransfers.new(paystackObj)
|
501
|
+
result = transfer.enableOtp
|
502
|
+
|
503
|
+
```
|
504
|
+
|
505
|
+
## Static methods
|
506
|
+
`PaystackTransactions`, `PaystackCustomers`, `PaystackPlans`, `PaystackSubaccounts`, `PaystackBanks` , `PaystackSubscriptions` , `PaystackSettlements`, `PaystackBalance`, and `PaystackTransfers` methods can be called statically, You just need to pass the paystack object as the first parameter e.g. `verify` method in `PaystackTransactions` can be called like this
|
507
|
+
|
508
|
+
|
509
|
+
```ruby
|
510
|
+
|
511
|
+
transaction_reference = "blablablabla-YOUR-VALID-UNIQUE-REFERENCE-HERE"
|
512
|
+
result = PaystackTransactions.verify(paystackObj, transaction_reference)
|
513
|
+
puts result['message']
|
514
|
+
|
515
|
+
```
|
57
516
|
|
58
|
-
## Development
|
59
517
|
|
60
|
-
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
61
518
|
|
62
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
63
519
|
|
64
520
|
## Contributing
|
65
521
|
|
@@ -69,4 +525,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/IkoroV
|
|
69
525
|
## License
|
70
526
|
|
71
527
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
72
|
-
|