recharge-api 0.0.1

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.
@@ -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: []