recharge-api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 38da2ffc86ba27bea660a00c153e74ffe72fc054
4
+ data.tar.gz: eed4ac714a4c95f27791c196fbe8c74a2e458b63
5
+ SHA512:
6
+ metadata.gz: 3b0e967d97687f1df95f7c981bae8846fd62736e246ff186087c40fb005f4711321652450475ccaff461b7ff915c3e7d4479c74e8021fe5794b67c7b1902a171
7
+ data.tar.gz: fab06c67606cd8052deb664ebaea1d1adc1b7615cd6777080c737ccfb05ea53bf0fc56e4ab02a4cab480c3c05c333b5297410d5fbb2702f1c27c3e6c3d44ec3a
@@ -0,0 +1,122 @@
1
+
2
+ # Created by https://www.gitignore.io/api/emacs,ruby
3
+
4
+ ### Emacs ###
5
+ # -*- mode: gitignore; -*-
6
+ *~
7
+ \#*\#
8
+ /.emacs.desktop
9
+ /.emacs.desktop.lock
10
+ *.elc
11
+ auto-save-list
12
+ tramp
13
+ .\#*
14
+
15
+ # Org-mode
16
+ .org-id-locations
17
+ *_archive
18
+
19
+ # flymake-mode
20
+ *_flymake.*
21
+
22
+ # eshell files
23
+ /eshell/history
24
+ /eshell/lastdir
25
+
26
+ # elpa packages
27
+ /elpa/
28
+
29
+ # reftex files
30
+ *.rel
31
+
32
+ # AUCTeX auto folder
33
+ /auto/
34
+
35
+ # cask packages
36
+ .cask/
37
+ dist/
38
+
39
+ # Flycheck
40
+ flycheck_*.el
41
+
42
+ # server auth directory
43
+ /server/
44
+
45
+ # projectiles files
46
+ .projectile
47
+ projectile-bookmarks.eld
48
+
49
+ # directory configuration
50
+ .dir-locals.el
51
+
52
+ # saveplace
53
+ places
54
+
55
+ # url cache
56
+ url/cache/
57
+
58
+ # cedet
59
+ ede-projects.el
60
+
61
+ # smex
62
+ smex-items
63
+
64
+ # company-statistics
65
+ company-statistics-cache.el
66
+
67
+ # anaconda-mode
68
+ anaconda-mode/
69
+
70
+ ### Ruby ###
71
+ *.gem
72
+ *.rbc
73
+ /.config
74
+ /coverage/
75
+ /InstalledFiles
76
+ /pkg/
77
+ /spec/reports/
78
+ /spec/examples.txt
79
+ /test/tmp/
80
+ /test/version_tmp/
81
+ /tmp/
82
+
83
+ # Used by dotenv library to load environment variables.
84
+ # .env
85
+
86
+ ## Specific to RubyMotion:
87
+ .dat*
88
+ .repl_history
89
+ build/
90
+ *.bridgesupport
91
+ build-iPhoneOS/
92
+ build-iPhoneSimulator/
93
+
94
+ ## Specific to RubyMotion (use of CocoaPods):
95
+ #
96
+ # We recommend against adding the Pods directory to your .gitignore. However
97
+ # you should judge for yourself, the pros and cons are mentioned at:
98
+ # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
99
+ #
100
+ # vendor/Pods/
101
+
102
+ ## Documentation cache and generated files:
103
+ /.yardoc/
104
+ /_yardoc/
105
+ /doc/
106
+ /rdoc/
107
+
108
+ ## Environment normalization:
109
+ /.bundle/
110
+ /vendor/bundle
111
+ /lib/bundler/man/
112
+
113
+ # for a library or gem, you might want to ignore these files since the code is
114
+ # intended to run in multiple environments; otherwise, check them in:
115
+ # Gemfile.lock
116
+ # .ruby-version
117
+ # .ruby-gemset
118
+
119
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
120
+ .rvmrc
121
+
122
+ # End of https://www.gitignore.io/api/emacs,ruby
@@ -0,0 +1,12 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.1
5
+ - 2.2
6
+ - 2.3
7
+ - 2.4
8
+
9
+ before_install: gem update bundler
10
+
11
+ notifications:
12
+ email: false
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in recharge-api.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 ScreenStaring
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ # ReCharge API Client
2
+
3
+ [![Build Status](https://travis-ci.org/ScreenStaring/recharge-api.svg?branch=master)](https://travis-ci.org/ScreenStaring/recharge-api)
4
+
5
+ Ruby client for [ReCharge Payments'](https://rechargepayments.com/developers)
6
+ recurring payments API for Shopify.
7
+
8
+ ## Installation
9
+
10
+ Ruby gems:
11
+
12
+ gem install recharge-api
13
+
14
+ Bundler:
15
+
16
+ gem "recharge-api", :require => "recharge"
17
+
18
+ ## Usage
19
+
20
+ ```rb
21
+ require "recharge"
22
+
23
+ ReCharge.api_key = "YOUR_KEY" # Can also use Recharge
24
+ data = {
25
+ :address_id => 123234321,
26
+ :customer_id => 565728,
27
+ # ... more stuff
28
+ :next_charge_scheduled_at => Time.new,
29
+ :properties => {
30
+ :name => "size",
31
+ :value => "medium"
32
+ }
33
+ }
34
+
35
+ subscription = ReCharge::Subscription.create(data)
36
+ subscription.address_id = 454343
37
+ subscription.save
38
+
39
+ # Or
40
+ ReCharge::Subscription.update(id, data)
41
+
42
+ subscription = ReCharge::Subscription.new(data)
43
+ subscription.save
44
+
45
+ order1 = ReCharge::Order.get(123123)
46
+ order1.line_items.each do |li|
47
+ p li.title
48
+ p li.quantity
49
+ end
50
+
51
+ order2 = ReCharge::Order.get(453321)
52
+ p "Different" if order1 != order2
53
+
54
+ JSON.dump(order2.to_h)
55
+
56
+ customers = ReCharge::Customer.list(:page => 10, :limit => 50)
57
+ customers.each do |customer|
58
+ addresses = ReCharge::Customer.addresses(customer.id)
59
+ # ...
60
+ end
61
+ ```
62
+
63
+ For complete documentation refer to the API docs: http://rdoc.info/gems/recharge-api
64
+
65
+ ## Rake Tasks for Webhook Management
66
+
67
+ Requiring `recharge/tasks` will include several Rake tasks for webhook management.
68
+ All tasks require `RECHARGE_API_KEY` be set.
69
+
70
+ The hooks are:
71
+
72
+ * `recharge:hook:create` - create webhook `HOOK` to be sent to `CALLBACK`
73
+ * `recharge:hooks:delete` - delete the webhook(s) given by `ID`
74
+ * `recharge:hooks:delete_all` - delete all webhooks
75
+ * `recharge:hooks:list` - list webhooks
76
+
77
+ ## License
78
+
79
+ Released under the MIT License: www.opensource.org/licenses/MIT
80
+
81
+ ---
82
+
83
+ Made by [ScreenStaring](http://screenstaring.com)
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1 @@
1
+ require "recharge"
@@ -0,0 +1,36 @@
1
+ require "recharge/classes"
2
+ require "recharge/version"
3
+
4
+ module Recharge
5
+ ENDPOINT = "api.rechargeapps.com".freeze
6
+ PORT = 443
7
+ TOKEN_HEADER = "X-Recharge-Access-Token".freeze
8
+ USER_AGENT = "ReCharge API Client v#{VERSION} (Ruby v#{RUBY_VERSION})"
9
+
10
+ Error = Class.new(StandardError)
11
+ ConnectionError = Class.new(Error)
12
+
13
+ #
14
+ # Raised when a non-2XX HTTP response is returned
15
+ #
16
+ class RequestError < Error
17
+ attr_accessor :errors
18
+ attr_accessor :status
19
+ attr_accessor :meta
20
+
21
+ def initialize(message, status, meta = nil, errors = nil)
22
+ super message
23
+ @status = status
24
+ @meta = meta || {}
25
+ @errors = errors || {}
26
+ end
27
+ end
28
+
29
+ class << self
30
+ attr_accessor :api_key
31
+ # If +true+ output HTTP request/response to stderr. Can also be an +IO+ instance to output to.
32
+ attr_accessor :debug
33
+ end
34
+ end
35
+
36
+ ReCharge = Recharge
@@ -0,0 +1,462 @@
1
+ require "json"
2
+ require "class2"
3
+ require "recharge/http_request"
4
+
5
+ class2 "Recharge", JSON.parse(<<-END) do
6
+ {
7
+ "subscription":
8
+ {
9
+ "id":10101,
10
+ "address_id":178918,
11
+ "customer_id":1438,
12
+ "created_at":"2017-02-28T20:31:29",
13
+ "updated_at":"2017-02-28 20:31:29",
14
+ "next_charge_scheduled_at":"2017-04-01T00:00:00",
15
+ "cancelled_at":null,
16
+ "product_title":"Sumatra Coffee",
17
+ "price":12,
18
+ "quantity":1,
19
+ "status":"ACTIVE",
20
+ "shopify_product_id":1255183683,
21
+ "shopify_variant_id":3844924611,
22
+ "sku":null,
23
+ "order_interval_unit":"day",
24
+ "order_interval_frequency":"30",
25
+ "charge_interval_frequency":"30",
26
+ "order_day_of_month":null,
27
+ "order_day_of_week":null,
28
+ "properties": []
29
+ },
30
+ "charge":
31
+ {
32
+ "address_id":178918,
33
+ "billing_address":{
34
+ "address1":"3030 Nebraska Avenue",
35
+ "address2":"",
36
+ "city":"Los Angeles",
37
+ "company":"",
38
+ "country":"United States",
39
+ "first_name":"Mike",
40
+ "last_name":"Flynn",
41
+ "phone":"",
42
+ "province":"California",
43
+ "zip":"90404"
44
+ },
45
+ "client_details": {
46
+ "browser_ip": null,
47
+ "user_agent": null
48
+ },
49
+ "created_at":"2017-03-01T19:52:11",
50
+ "customer_hash":null,
51
+ "customer_id":10101,
52
+ "first_name":"Mike",
53
+ "id":1843,
54
+ "last_name":"Flynn",
55
+ "line_items":[
56
+ {
57
+ "grams":0,
58
+ "price":100.0,
59
+ "properties":[],
60
+ "quantity":1,
61
+ "shopify_product_id": "1255183683",
62
+ "shopify_variant_id":"3844924611",
63
+ "sku":"",
64
+ "title": "Milk 10% Off Auto renew",
65
+ "variant_title": "a / b",
66
+ "vendor": "Example Storeeeeeee",
67
+ "subscription_id":14562
68
+ }
69
+ ],
70
+ "processed_at":"2014-11-20T00:00:00",
71
+ "scheduled_at":"2014-11-20T00:00:01",
72
+ "shipments_count":null,
73
+ "shipping_address":{
74
+ "address1":"3030 Nebraska Avenue",
75
+ "address2":"",
76
+ "city":"Los Angeles",
77
+ "company":"",
78
+ "country":"United States",
79
+ "first_name":"Mike",
80
+ "last_name":"Flynn",
81
+ "phone":"3103843698",
82
+ "province":"California",
83
+ "zip":"90404"
84
+ },
85
+ "shopify_order_id":"281223307",
86
+ "status":"SUCCESS",
87
+ "total_price":446.00,
88
+ "updated_at":"2016-09-05T09:19:29"
89
+ },
90
+ "customer":
91
+ {
92
+ "id": 1438,
93
+ "hash": "143806234a9ff87a8d9e",
94
+ "shopify_customer_id": null,
95
+ "email": "mike@gmail.com",
96
+ "created_at": "2018-01-10T11:00:00",
97
+ "updated_at": "2017-01-11T13:16:19",
98
+ "first_name": "Mike",
99
+ "last_name": "Flynn",
100
+ "billing_first_name": "Mike",
101
+ "billing_last_name": "Flynn",
102
+ "billing_company": null,
103
+ "billing_address1": "3030 Nebraska Avenue",
104
+ "billing_address2": null,
105
+ "billing_zip": "90404",
106
+ "billing_city": "Los Angeles",
107
+ "billing_province": "California",
108
+ "billing_country": "United States",
109
+ "billing_phone": "3103843698",
110
+ "processor_type": null,
111
+ "status": "FOO"
112
+ },
113
+ "order": {
114
+ "id":7271806,
115
+ "customer_id":10101,
116
+ "address_id":178918,
117
+ "charge_id":9519316,
118
+ "transaction_id":"ch_19sdP2J2zqHvZRd1hqkeGANO",
119
+ "shopify_order_id":"5180645510",
120
+ "shopify_order_number":5913,
121
+ "created_at":"2017-03-01T14:46:26",
122
+ "updated_at":"2017-03-01T14:46:26",
123
+ "scheduled_at":"2017-03-01T00:00:00",
124
+ "processed_at":"2017-03-01T14:46:26",
125
+ "status":"SUCCESS",
126
+ "charge_status":"SUCCESS",
127
+ "type":"CHECKOUT",
128
+ "first_name":"Mike",
129
+ "last_name":"Flynn",
130
+ "email":"mike@gmail.com",
131
+ "payment_processor":"stripe",
132
+ "address_is_active":1,
133
+ "is_prepaid":0,
134
+ "line_items":[
135
+ {
136
+ "subscription_id":10101,
137
+ "shopify_product_id":"1255183683",
138
+ "shopify_variant_id":"3844924611",
139
+ "variant_title":"Sumatra",
140
+ "title":"Sumatra Latte",
141
+ "quantity":1,
142
+ "properties":[]
143
+ }
144
+ ],
145
+ "total_price":18.00,
146
+ "shipping_address":{
147
+ "address1":"1933 Manning",
148
+ "address2":"204",
149
+ "city":"los angeles",
150
+ "province":"California",
151
+ "first_name":"mike",
152
+ "last_name":"flynn",
153
+ "zip":"90025",
154
+ "company":"bootstrap",
155
+ "phone":"3103103101",
156
+ "country":"United States"
157
+ },
158
+ "billing_address":{
159
+ "address1":"1933 Manning",
160
+ "address2":"204",
161
+ "city":"los angeles",
162
+ "province":"California",
163
+ "first_name":"mike",
164
+ "last_name":"flynn",
165
+ "zip":"90025",
166
+ "company":"bootstrap",
167
+ "phone":"3103103101",
168
+ "country":"United States"
169
+ }
170
+ },
171
+ "webhook":
172
+ {
173
+ "id":6,
174
+ "address":"https://request.in/foo",
175
+ "topic":"order/create"
176
+ },
177
+ "address":{
178
+ "id":3411137,
179
+ "address1":"1933 Manning",
180
+ "address2":"204",
181
+ "city":"los angeles",
182
+ "province":"California",
183
+ "first_name":"mike",
184
+ "last_name":"flynn",
185
+ "zip":"90025",
186
+ "company":"bootstrap",
187
+ "phone":"3103103101",
188
+ "country":"United States"
189
+ }
190
+ }
191
+ END
192
+ def meta=(meta)
193
+ @meta = meta
194
+ end
195
+
196
+ def meta
197
+ @meta ||= {}
198
+ end
199
+
200
+ private
201
+
202
+ def self.instance(response)
203
+ args = response[self::SINGLE]
204
+ args["meta"] = response["meta"]
205
+ new(args)
206
+ end
207
+ end
208
+
209
+ module Recharge
210
+ module Persistable # :nodoc:
211
+ def save
212
+ data = to_h
213
+ data.delete("id")
214
+
215
+ if id
216
+ self.class.update(id, data)
217
+ else
218
+ self.id = self.class.create(data).id
219
+ end
220
+ end
221
+ end
222
+
223
+ class Address
224
+ PATH = "/addresses".freeze
225
+ SINGLE = "address".freeze
226
+ COLLECTION = "addresses".freeze
227
+
228
+ extend HTTPRequest::Get
229
+ extend HTTPRequest::Update
230
+
231
+ #
232
+ # Persist the updated address
233
+ #
234
+ # === Errors
235
+ #
236
+ # Recharge::ConnectionError, Recharge::RequestError
237
+ #
238
+ def save
239
+ data = to_h
240
+ data.delete("id")
241
+ self.class.update(id, data)
242
+ end
243
+
244
+ # Validate an address
245
+ #
246
+ # === Arguments
247
+ #
248
+ # [data (Hash)] Address to validate, see: https://developer.rechargepayments.com/?shell#validate-address
249
+ #
250
+ # === Returns
251
+ #
252
+ # [Hash] Validated and sometimes updated address
253
+ #
254
+ # === Errors
255
+ #
256
+ # Recharge::ConnectionError, Recharge::RequestError
257
+ #
258
+ # If the address is invalid a Recharge::RequestError is raised. The validation
259
+ # errors can be retrieved via Recharge::RequestError#errors
260
+ #
261
+ def self.validate(data)
262
+ POST(join("validate"), data)
263
+ end
264
+ end
265
+
266
+ class Customer
267
+ PATH = "/customers".freeze
268
+ SINGLE = "customer".freeze
269
+ COLLECTION = "customers".freeze
270
+
271
+ extend HTTPRequest::Create
272
+ extend HTTPRequest::Get
273
+ extend HTTPRequest::Update
274
+ extend HTTPRequest::List
275
+ extend HTTPRequest::Count
276
+
277
+ include Persistable
278
+
279
+ # Retrieve all of a customer's addresses
280
+ #
281
+ # === Arguments
282
+ #
283
+ # [id (Fixnum)] Customer ID
284
+ #
285
+ # === Errors
286
+ #
287
+ # ConnectionError, RequestError
288
+ #
289
+ # === Returns
290
+ #
291
+ # [Array[Recharge::Address]] The customer's addresses
292
+ #
293
+ def self.addresses(id)
294
+ id_required!(id)
295
+ data = GET(join(id, Address::COLLECTION))
296
+ (data[Address::COLLECTION] || []).map do |d|
297
+ address = Address.new(d)
298
+ address.meta = data["meta"]
299
+ address
300
+ end
301
+ end
302
+
303
+ # Create a new address
304
+ #
305
+ # === Arguments
306
+ #
307
+ # [id (Fixnum)] Customer ID
308
+ # [address (Hash)] Address attributes, see: https://developer.rechargepayments.com/?shell#create-address
309
+ #
310
+ # === Returns
311
+ #
312
+ # [Recharge::Address] The created address
313
+ #
314
+ # === Errors
315
+ #
316
+ # Recharge::ConnectionError, Recharge::RequestError
317
+ #
318
+ def self.create_address(id, address)
319
+ id_required!(id)
320
+ data = POST(join(id, Address::COLLECTION), address)
321
+ address = Address.new(data[Address::SINGLE])
322
+ address.meta = data["meta"]
323
+ address
324
+ end
325
+ end
326
+
327
+ class Charge
328
+ PATH = "/charges".freeze
329
+ SINGLE = "charge".freeze
330
+ COLLECTION = "charges".freeze
331
+
332
+ extend HTTPRequest::Count
333
+ extend HTTPRequest::Get
334
+ extend HTTPRequest::List
335
+
336
+ def self.list(options = nil)
337
+ super(convert_date_params(options, :date_min, :date_max))
338
+ end
339
+
340
+ def self.change_next_charge_date(id, date)
341
+ path = join(id, "change_next_charge_date")
342
+ instance(POST(path, :next_charge_date => date_param(date)))
343
+ end
344
+
345
+ def self.skip(id, subscription_id)
346
+ path = join(id, "skip")
347
+ instance(POST(path, :subscription_id => subscription_id))
348
+ end
349
+ end
350
+
351
+ class Order
352
+ PATH = "/orders".freeze
353
+ SINGLE = "order".freeze
354
+ COLLECTION = "orders".freeze
355
+
356
+ extend HTTPRequest::Count
357
+ extend HTTPRequest::Get
358
+ extend HTTPRequest::List
359
+
360
+ def self.count(options = nil)
361
+ super(convert_date_params(options, :created_at_max, :created_at_min, :date_min, :date_max))
362
+ end
363
+
364
+ def self.change_date(id, date)
365
+ id_required!(id)
366
+ instance(POST(join(id, "change_date"), :shipping_date => date_param(date)))
367
+ end
368
+
369
+ def self.update_shopify_variant(id, old_variant_id, new_varient_id)
370
+ id_required!(id)
371
+ path = join(id, "update_shopify_variant", old_variant_id)
372
+ instance(POST(path, :new_shopify_variant_id => new_varient_id))
373
+ end
374
+ end
375
+
376
+ class Subscription
377
+ PATH = "/subscriptions".freeze
378
+ SINGLE = "subscription".freeze
379
+ COLLECTION = "subscriptions".freeze
380
+
381
+ extend HTTPRequest::Create
382
+ extend HTTPRequest::Get
383
+ extend HTTPRequest::Update
384
+ extend HTTPRequest::List
385
+
386
+ include Persistable
387
+
388
+ #
389
+ # Activate a subscription
390
+ #
391
+ # === Arguments
392
+ #
393
+ # [id (Integer)] ID of subscription to cancel
394
+ #
395
+ # === Returns
396
+ #
397
+ # [Recharge::Subscription] The activated subscription
398
+ #
399
+ # === Errors
400
+ #
401
+ # Recharge::ConnectionError, Recharge::RequestError
402
+ #
403
+ # If the subscription was already activated a Recharge::RequestError will be raised
404
+ #
405
+ def self.activate(id)
406
+ id_required!(id)
407
+ instance(POST(join(id, "activate"), :status => "active"))
408
+ end
409
+
410
+ #
411
+ # Cancel a subscription
412
+ #
413
+ # === Arguments
414
+ #
415
+ # [id (Integer)] ID of subscription to cancel
416
+ # [reason (String)] Reason for the cancellation
417
+ #
418
+ # === Returns
419
+ #
420
+ # [Recharge::Subscription] The canceled subscription
421
+ #
422
+ # === Errors
423
+ #
424
+ # Recharge::ConnectionError, Recharge::RequestError
425
+ #
426
+ # If the subscription was already canceled a Recharge::RequestError will be raised
427
+ #
428
+ def self.cancel(id, reason)
429
+ id_required!(id)
430
+ instance(POST(join(id, "cancel"), :cancellation_reason => reason))
431
+ end
432
+
433
+ def self.set_next_charge_date(id, date)
434
+ id_required!(id)
435
+ instance(POST(join(id, "set_next_charge_date"), :date => date_param(date)))
436
+ end
437
+
438
+ def self.list(options = nil)
439
+ #options[:status] = options[:status].upcase if options[:status]
440
+ super(convert_date_params(options, :created_at, :created_at_max, :updated_at, :updated_at_max))
441
+ end
442
+ end
443
+
444
+ class Webhook
445
+ PATH = "/webhooks".freeze
446
+ COLLECTION = "webhooks".freeze
447
+ SINGLE = "webhook".freeze
448
+
449
+ extend HTTPRequest::Create
450
+ extend HTTPRequest::Delete
451
+ extend HTTPRequest::Get
452
+ extend HTTPRequest::List
453
+ extend HTTPRequest::Update
454
+
455
+ include Persistable
456
+
457
+ def delete
458
+ self.class.delete(id)
459
+ true
460
+ end
461
+ end
462
+ end
@@ -0,0 +1,223 @@
1
+ require "net/http"
2
+ require "uri"
3
+ require "json"
4
+
5
+ # For iso8601
6
+ require "date"
7
+ require "time"
8
+
9
+ module Recharge
10
+ module HTTPRequest # :nodoc:
11
+
12
+ protected
13
+
14
+ def DELETE(path, data = {})
15
+ request(Net::HTTP::Delete.new(path), data)
16
+ end
17
+
18
+ def GET(path, data = {})
19
+ path += sprintf("?%s", URI.encode_www_form(data)) if data && data.any?
20
+ request(Net::HTTP::Get.new(path))
21
+ end
22
+
23
+ def POST(path, data = {})
24
+ request(Net::HTTP::Post.new(path), data)
25
+ end
26
+
27
+ def PUT(path, data = {})
28
+ request(Net::HTTP::Put.new(path), data)
29
+ end
30
+
31
+ def id_required!(id)
32
+ raise ArgumentError, "id required" if id.nil? || id.to_s.strip.empty?
33
+ end
34
+
35
+ def join(*parts)
36
+ parts.unshift(self::PATH).join("/")
37
+ end
38
+
39
+ def convert_date_params(options, *keys)
40
+ return unless options
41
+
42
+ options = options.dup
43
+ keys.each do |key|
44
+ options[key] = date_param(options[key]) if options.include?(key)
45
+ end
46
+
47
+ options
48
+ end
49
+
50
+ def date_param(date)
51
+ # ReCharge doesn't accept 8601, 500s on zone specifier
52
+ # date.respond_to?(:iso8601) ? date.iso8601 : date
53
+ date.respond_to?(:strftime) ? date.strftime("%Y-%m-%dT%H:%M:%S") : date
54
+ end
55
+
56
+ private
57
+
58
+ def request(req, data = {})
59
+ req[TOKEN_HEADER] = ReCharge.api_key || ""
60
+ req["User-Agent"] = USER_AGENT
61
+
62
+ if req.request_body_permitted? && data && data.any?
63
+ req.body = data.to_json
64
+ req["Content-Type"] = "application/json"
65
+ end
66
+
67
+ connection.start do |http|
68
+ res = http.request(req)
69
+ data = res["Content-Type"] == "application/json" ? parse_json(res.body) : {}
70
+ data["meta"] = { "id" => res["X-Request-Id"], "limit" => res["X-Recharge-Limit"] }
71
+
72
+ return data if res.code[0] == "2" && !data["warning"] && !data["error"]
73
+
74
+ message = data["warning"] || data["error"] || "#{res.code} - #{res.message}"
75
+ raise RequestError.new(message, res.code, data["meta"], data["errors"])
76
+ end
77
+ rescue Net::ReadTimeout, IOError, SocketError, SystemCallError => e
78
+ raise ConnectionError, "connection failure: #{e}"
79
+ end
80
+
81
+ def connection
82
+ unless defined? @request
83
+ @request = Net::HTTP.new(ENDPOINT, PORT)
84
+ @request.use_ssl = true
85
+ end
86
+
87
+ if !Recharge.debug
88
+ @request.set_debug_output(nil)
89
+ else
90
+ @request.set_debug_output(
91
+ Recharge.debug.is_a?(IO) ? Recharge.debug : $stderr
92
+ )
93
+ end
94
+
95
+ @request
96
+ end
97
+
98
+ def parse_json(s)
99
+ JSON.parse(s)
100
+ rescue JSON::ParserError => e
101
+ raise Error, "failed to parse JSON response: #{e}"
102
+ end
103
+
104
+ #
105
+ # Make a count request to the included/extended class' endpoint
106
+ #
107
+ # === Arguments
108
+ #
109
+ # [options (Hash)] Optional arguments to filter the count on.
110
+ #
111
+ # See the appropriate count call in {ReCharge's documentation}[https://developer.rechargepayments.com/] for valid options.
112
+ #
113
+ # === Returns
114
+ #
115
+ # +Fixnum+ of the resulting count
116
+ #
117
+ # === Errors
118
+ #
119
+ # Recharge::ConnectionError, Recharge::RequestError
120
+ #
121
+ module Count
122
+ include HTTPRequest
123
+
124
+ def count(options = nil)
125
+ GET(join("count"), options)["count"]
126
+ end
127
+ end
128
+
129
+ #
130
+ # Create an new record for the included/extended entity
131
+ #
132
+ # === Arguments
133
+ #
134
+ # [data (Hash)] New record's attributes
135
+ #
136
+ # See the appropriate create call in {ReCharge's documentation}[https://developer.rechargepayments.com/] for valid attributes
137
+ #
138
+ # === Returns
139
+ #
140
+ # An instance of the created entity
141
+ #
142
+ # === Errors
143
+ #
144
+ # Recharge::ConnectionError, Recharge::RequestError
145
+ #
146
+ module Create
147
+ include HTTPRequest
148
+
149
+ def create(data)
150
+ new(POST(self::PATH, data)[self::SINGLE])
151
+ end
152
+ end
153
+
154
+ #
155
+ # Delete a record for included/extended entity
156
+ #
157
+ # === Arguments
158
+ #
159
+ # [id (Fixnum)] ID of the record to delete
160
+ #
161
+ # === Returns
162
+ #
163
+ # An instance of the deleted entity
164
+ #
165
+ # === Errors
166
+ #
167
+ # Recharge::ConnectionError, Recharge::RequestError
168
+ #
169
+ module Delete
170
+ include HTTPRequest
171
+
172
+ def delete(id)
173
+ id_required!(id)
174
+ DELETE(join(id))
175
+ end
176
+ end
177
+
178
+ module Get
179
+ include HTTPRequest
180
+
181
+ def get(id)
182
+ id_required!(id)
183
+ new(GET(join(id))[self::SINGLE])
184
+ end
185
+ end
186
+
187
+ #
188
+ # Retrieve a page of records for the included/extended class'
189
+ #
190
+ # === Arguments
191
+ #
192
+ # [options (Hash)] Optional arguments to filter the result set on.
193
+ #
194
+ # In most cases +:page+ and +:limit+ are accepted but see
195
+ # the appropriate call in {ReCharge's documentation}[https://developer.rechargepayments.com/] for valid options.
196
+ #
197
+ # === Returns
198
+ #
199
+ # +Array+ of record instances
200
+ #
201
+ # === Errors
202
+ #
203
+ # Recharge::ConnectionError, Recharge::RequestError
204
+ #
205
+ module List
206
+ include HTTPRequest
207
+
208
+ def list(options = nil)
209
+ data = GET(self::PATH, options)
210
+ (data[self::COLLECTION] || []).map { |d| new(d.merge("meta" => data["meta"])) }
211
+ end
212
+ end
213
+
214
+ module Update
215
+ include HTTPRequest
216
+
217
+ def update(id, data)
218
+ id_required!(id)
219
+ new(PUT(join(id), data)[self::SINGLE])
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,65 @@
1
+ require "rake"
2
+ require "recharge"
3
+
4
+ namespace :recharge do
5
+ namespace :hooks do
6
+ task :_setup_recharge do
7
+ abort "RECHARGE_API_KEY required" unless ENV["RECHARGE_API_KEY"]
8
+ ReCharge.api_key = ENV["RECHARGE_API_KEY"]
9
+ end
10
+
11
+ desc "List webhooks for RECHARGE_API_KEY"
12
+ task :list => :_setup_recharge do
13
+ format = "%-5s %-25s %-80s\n"
14
+ printf format, "ID", "Hook", "URL"
15
+
16
+ Recharge::Webhook.list.each do |hook|
17
+ printf format, hook.id, hook.topic, hook.address
18
+ end
19
+ end
20
+
21
+ desc "Delete webhooks for RECHARGE_API_KEY"
22
+ task :delete_all => :_setup_recharge do
23
+ Recharge::Webhook.list.each { |hook| hook.class.delete(hook.id) }
24
+ end
25
+
26
+ desc "Delete the webhooks given by the ID(s) in ID for RECHARGE_API_KEY"
27
+ task :delete => :_setup_recharge do
28
+ ids = ENV["ID"].to_s.strip.split(",")
29
+ abort "ID required" unless ids.any?
30
+
31
+ ids.each do |id|
32
+ puts "Deleting webhook #{id}"
33
+ Recharge::Webhook.delete(id)
34
+ end
35
+ end
36
+
37
+ desc "Create webhook HOOK to be sent to CALLBACK for RECHARGE_API_KEY"
38
+ task :create => :_setup_recharge do
39
+ known_hooks = %w[
40
+ subscription/created
41
+ subscription/updated
42
+ subscription/activated
43
+ subscription/cancelled
44
+ customer/created
45
+ customer/updated
46
+ order/created
47
+ charge/created
48
+ charge/paid
49
+ ]
50
+
51
+ abort "CALLBACK required" unless ENV["CALLBACK"]
52
+ abort "HOOK required" unless ENV["HOOK"]
53
+ abort "unknown hook #{ENV["HOOK"]}" unless known_hooks.include?(ENV["HOOK"])
54
+
55
+ puts "Creating webhook #{ENV["HOOK"]} for #{ENV["CALLBACK"]}"
56
+
57
+ hook = Recharge::Webhook.create(
58
+ :topic => ENV["HOOK"],
59
+ :address => ENV["CALLBACK"]
60
+ )
61
+
62
+ puts "Created hook ##{hook.id}"
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module Recharge
2
+ VERSION = "0.0.1".freeze
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "recharge/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "recharge-api"
8
+ spec.version = Recharge::VERSION
9
+ spec.authors = ["Skye Shaw"]
10
+ spec.email = ["skye.shaw@gmail.com"]
11
+
12
+ spec.summary = %q{Client for ReCharge Payments API}
13
+ spec.description = %q{Client for ReCharge Payments recurring payments API for Shopify}
14
+ spec.homepage = "https://github.com/ScreenStaring/recharge-api"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "class2", "~> 0.3.0"
23
+ spec.add_development_dependency "webmock", "~> 3.0"
24
+ spec.add_development_dependency "bundler", "~> 1.12"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: recharge-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Skye Shaw
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: class2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.3.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.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: webmock
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Client for ReCharge Payments recurring payments API for Shopify
84
+ email:
85
+ - skye.shaw@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - LICENSE.txt
94
+ - README.md
95
+ - Rakefile
96
+ - lib/recharge-api.rb
97
+ - lib/recharge.rb
98
+ - lib/recharge/classes.rb
99
+ - lib/recharge/http_request.rb
100
+ - lib/recharge/tasks.rb
101
+ - lib/recharge/version.rb
102
+ - recharge-api.gemspec
103
+ homepage: https://github.com/ScreenStaring/recharge-api
104
+ licenses:
105
+ - MIT
106
+ metadata: {}
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: '0'
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">="
119
+ - !ruby/object:Gem::Version
120
+ version: '0'
121
+ requirements: []
122
+ rubyforge_project:
123
+ rubygems_version: 2.6.14
124
+ signing_key:
125
+ specification_version: 4
126
+ summary: Client for ReCharge Payments API
127
+ test_files: []