stripe2qb 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8987570f2160c419fbbe9dcac2c8d53addf95725
4
+ data.tar.gz: dedad6c810f7fb2773e5ee6cd55aa36ed63a6c47
5
+ SHA512:
6
+ metadata.gz: 9fee3c5cff6cca199d44f772e7b9a3081adfd7b53d662f8f7709d3bb5267d02dce641a451a16d9ce30b089e02431c12c319faf2f5745680c4feac0f54d7ac83c
7
+ data.tar.gz: c7897d522c869f8bba578aa4fc7478f5cf3e88d4a40d871de43ded84fbf15add4c399cbf7c7e7957ad8e5105f1461dedf856be61e5315c9ae52a9c05732a0790
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at anthony@anthonywang.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in stripe2qb.gemspec
4
+ gemspec
5
+
6
+ gem 'stripe', git: 'https://github.com/stripe/stripe-ruby'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Anthony Wang
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.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # Stripe2QB
2
+
3
+ [Stripe2QB](https://github.com/wangthony/stripe2qb) reads your Stripe
4
+ transactions and imports them into Quickbooks.
5
+
6
+ Stripe2QB is built on top of the [stripe](https://github.com/stripe/stripe-ruby)
7
+ and [quickbooks-ruby](https://github.com/ruckus/quickbooks-ruby) gems.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'stripe2qb'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install stripe2qb
24
+
25
+ ## Configuration
26
+
27
+ The suggested way to configure Stripe2QB is by pointing it to a YAML file.
28
+ There is a template file at `config/stripe2qb.yml` that you can copy into your
29
+ application. See that file for information on configuration options.
30
+
31
+ ```ruby
32
+ s2qb = Stripe2QB.new # look for the default at config/stripe2qb.yml
33
+
34
+ s2qb = Stripe2QB.new('path/to/config.yml') # use a different config file path
35
+
36
+ s2qb = Stripe2QB.new(quickbooks_api: { ... }, stripe_api: { ... }) # use a Hash
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ The `#process` method looks for deposited (paid)
42
+ [Stripe Transfers](https://stripe.com/docs/api#transfers) on a certain date
43
+ (or date range), and converts them into
44
+ [Quickbooks Deposits](https://developer.intuit.com/docs/api/accounting/deposit).
45
+
46
+ ```ruby
47
+ s2qb = Stripe2QB.new
48
+
49
+ # process transfers on or after October 1st, 2016
50
+ s2qb.process('2016-10-01')
51
+
52
+ # process transfers in the month of October, 2016
53
+ s2qb.process('2016-10-01', '2016-11-01')
54
+ ```
55
+
56
+ In addition to creating a Deposit, all
57
+ [Stripe Charges](https://stripe.com/docs/api#charges) associated with each
58
+ Transfer are converted into
59
+ [Quickbooks SalesReceipts](https://developer.intuit.com/docs/api/accounting/salesreceipt),
60
+ and all [Stripe Refunds](https://stripe.com/docs/api#refunds) associated with
61
+ each Transfer are converted into
62
+ [Quickbooks RefundReceipts](https://developer.intuit.com/docs/api/accounting/refundreceipt).
63
+ The SalesReceipts and RefundReceipts are linked to the corresponding Deposit.
64
+
65
+ Also, each Deposit will contain a line item for the Stripe processing fees and
66
+ fee refunds included in the Transfer.
67
+
68
+ You can safely re-process dates without creating duplicate records, as long as
69
+ you do not edit the `payment_ref_number` or `private_note` fields in records
70
+ created by Stripe2QB.
71
+
72
+ ## Disclaimer
73
+
74
+ Even though this gem is used in production applications, and most of the API
75
+ operations involved are reversible / fixable, there may be cases where API calls
76
+ made by this gem have unforeseen consequences.
77
+
78
+ Therefore, you assume all liability for any unwanted changes to data caused by
79
+ use of this gem. The author and contributors assume no responsibility. **Use at your own risk.**
80
+
81
+ ## Development
82
+
83
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
84
+
85
+ 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).
86
+
87
+ ## Contributing
88
+
89
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
90
+
91
+ ## License
92
+
93
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -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
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "stripe2qb"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/config/.keep ADDED
File without changes
@@ -0,0 +1,36 @@
1
+ quickbooks_api:
2
+ # find realm ID at https://developer.intuit.com/v2/apiexplorer?apiname=V3QBO
3
+ realm_id: 'your Quickbooks API company ID'
4
+
5
+ # find OAuth consumer key & secret at https://developer.intuit.com/v2/ui#/app/dashboard
6
+ # (you have to sign up for a developer account and create an app first - see http://www.mooreds.com/wordpress/archives/1396)
7
+ oauth_consumer_key: 'your Quickbooks OAuth consumer key'
8
+ oauth_consumer_secret: 'your Quickbooks OAuth consumer secret'
9
+
10
+ # generate token & secret at https://appcenter.intuit.com/Playground/OAuth/IA
11
+ # (set the duration to 15552000 = 180 days * 24 * 60 * 60)
12
+ api_access_token: 'your Quickbooks API access token'
13
+ api_access_secret: 'your Quickbooks API access secret'
14
+
15
+ # name or Quickbooks ID of the Customer for Stripe orders
16
+ receipt_customer: 'Stripe Customer' # String name or integer ID
17
+
18
+ # name or Quickbooks ID of the Item (Product/Service) for Stripe orders
19
+ receipt_item: 'Stripe Sales' # String name or integer ID
20
+
21
+ # name or Quickbooks ID of the Payment Method for Stripe transfers
22
+ receipt_payment_method: 'Stripe' # String name or integer ID
23
+
24
+ # name or Quickbooks ID of the bank Account where Stripe transfers are deposited from
25
+ receipt_account: 'Undeposited Funds' # String name or integer ID
26
+
27
+ # name or Quickbooks ID of the bank Account where Stripe transfers are deposited to
28
+ deposit_account: 'Business Checking' # String name or integer ID
29
+
30
+ # name or Quickbooks ID of the Vendor that you want to assign Stripe transfers to
31
+ deposit_fees_vendor: 'Stripe Payments Platform' # String name or integer ID
32
+
33
+ # name or Quickbooks ID of the Account where you want to book Stripe processing fees
34
+ deposit_fees_account: 'Stripe Merchant Fees' # String name or integer ID
35
+ stripe_api:
36
+ api_key: 'your Stripe API key'
@@ -0,0 +1,33 @@
1
+ require 'stripe2qb/options_reading'
2
+ require 'yaml'
3
+
4
+ module Stripe2QB
5
+ class ConfigurationError < StandardError; end
6
+
7
+ class Configuration
8
+ include OptionsReading
9
+
10
+ attr_reader :quickbooks_api
11
+ attr_reader :stripe_api
12
+
13
+ def initialize(options)
14
+ if options.is_a?(String)
15
+ file = File.open(options)
16
+ options = YAML.load(file.read)
17
+ end
18
+
19
+ quickbooks_api_options = get_required_from_options('quickbooks_api', options)
20
+ @quickbooks_api = QuickbooksApi.new(quickbooks_api_options)
21
+
22
+ stripe_api_options = get_required_from_options('stripe_api', options)
23
+ @stripe_api = StripeApi.new(stripe_api_options)
24
+ end
25
+
26
+ def process(start_date, end_date = nil)
27
+ transfers = stripe_api.get_transfers(start_date, end_date || Date.today)
28
+ transfers.each do |transfer|
29
+ Converters::TransferToDeposit.new(transfer, self).find_or_create
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,43 @@
1
+ module Stripe2QB
2
+ module Converters
3
+ class Base
4
+ attr_reader :quickbooks_api
5
+ attr_reader :stripe_api
6
+
7
+ def initialize(configuration)
8
+ @quickbooks_api = configuration.quickbooks_api
9
+ @stripe_api = configuration.stripe_api
10
+ end
11
+
12
+ def find
13
+ raise 'not implemented'
14
+ end
15
+
16
+ def exists?
17
+ !find.nil?
18
+ end
19
+
20
+ def create!
21
+ raise 'not implemented'
22
+ end
23
+
24
+ def find_or_create
25
+ find || create!
26
+ end
27
+
28
+ def delete!
29
+ raise 'not implemented'
30
+ end
31
+
32
+ protected
33
+
34
+ def format_date(value)
35
+ Time.at(value).in_time_zone('UTC').to_date.to_s
36
+ end
37
+
38
+ def format_amount(value)
39
+ (value / 100.0).round(2)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,59 @@
1
+ module Stripe2QB
2
+ module Converters
3
+ class ChargeToSalesReceipt < Base
4
+ attr_reader :charge
5
+ attr_reader :sales_receipt
6
+
7
+ def initialize(charge, configuration)
8
+ super(configuration)
9
+
10
+ @charge = charge
11
+ @sales_receipt = build_sales_receipt
12
+ end
13
+
14
+ def find
15
+ @found ||= quickbooks_api.sales_receipt_service.find_by(:payment_ref_num, charge.id).first
16
+ end
17
+
18
+ def create!
19
+ raise "SalesReceipt for #{charge.id} already exists: #{find.id}" if exists?
20
+
21
+ quickbooks_api.sales_receipt_service.create(sales_receipt)
22
+ end
23
+
24
+ def delete!
25
+ return false unless exists?
26
+
27
+ result = quickbooks_api.sales_receipt_service.delete(find)
28
+ @found = nil
29
+
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ def build_sales_receipt
36
+ sales_receipt = Quickbooks::Model::SalesReceipt.new
37
+ sales_receipt.customer_id = quickbooks_api.receipt_customer.id
38
+ sales_receipt.payment_method_id = quickbooks_api.receipt_payment_method.id
39
+ sales_receipt.deposit_to_account_id = quickbooks_api.receipt_account.id
40
+ sales_receipt.txn_date = format_date(charge.created)
41
+ sales_receipt.payment_ref_number = charge.id
42
+ sales_receipt.private_note = "Imported by Stripe2QB: #{charge.id}"
43
+ sales_receipt.auto_doc_number!
44
+
45
+ line_item = Quickbooks::Model::Line.new
46
+ line_item.amount = amount = format_amount(charge.amount)
47
+ line_item.description = charge.description
48
+ line_item.sales_item! do |detail|
49
+ detail.quantity = 1
50
+ detail.unit_price = amount
51
+ detail.item_id = quickbooks_api.receipt_item.id
52
+ end
53
+ sales_receipt.line_items << line_item
54
+
55
+ sales_receipt
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,68 @@
1
+ module Stripe2QB
2
+ module Converters
3
+ class RefundToRefundReceipt < Base
4
+ attr_reader :refund
5
+ attr_reader :refund_receipt
6
+
7
+ def initialize(refund, configuration)
8
+ super(configuration)
9
+
10
+ @refund = refund
11
+ @refund_receipt = build_refund_receipt
12
+ end
13
+
14
+ def find
15
+ return @found if @found
16
+
17
+ # Quickbooks doesn't have payment_ref_number on RefundReceipts, so we
18
+ # have to search for a match on private_note...
19
+ # first narrow down results by txn_date
20
+ refund_receipts = quickbooks_api.refund_receipt_service.find_by(:txn_date, format_date(refund.created))
21
+ refund_receipts.each do |refund_receipt|
22
+ return @found = refund_receipt if refund_receipt.private_note =~ /#{refund.id}/
23
+ end
24
+
25
+ nil
26
+ end
27
+
28
+ def create!
29
+ raise "RefundReceipt for #{refund.id} already exists: #{find.id}" if exists?
30
+
31
+ quickbooks_api.refund_receipt_service.create(refund_receipt)
32
+ end
33
+
34
+ def delete!
35
+ return false unless exists?
36
+
37
+ result = quickbooks_api.refund_receipt_service.delete(find)
38
+ @found = nil
39
+
40
+ result
41
+ end
42
+
43
+ private
44
+
45
+ def build_refund_receipt
46
+ refund_receipt = Quickbooks::Model::RefundReceipt.new
47
+ refund_receipt.customer_id = quickbooks_api.receipt_customer.id
48
+ refund_receipt.payment_method_id = quickbooks_api.receipt_payment_method.id
49
+ refund_receipt.deposit_to_account_id = quickbooks_api.receipt_account.id
50
+ refund_receipt.txn_date = format_date(refund.created)
51
+ refund_receipt.private_note = "Imported by Stripe2QB: #{refund.id}"
52
+ refund_receipt.auto_doc_number!
53
+
54
+ line_item = Quickbooks::Model::Line.new
55
+ line_item.amount = amount = format_amount(-1 * refund.amount) # convert to positive number for RefundReceipt
56
+ line_item.description = refund.description
57
+ line_item.sales_item! do |detail|
58
+ detail.quantity = 1
59
+ detail.unit_price = amount
60
+ detail.item_id = quickbooks_api.receipt_item.id
61
+ end
62
+ refund_receipt.line_items << line_item
63
+
64
+ refund_receipt
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,157 @@
1
+ module Stripe2QB
2
+ module Converters
3
+ class TransferToDeposit < Base
4
+ attr_reader :transfer
5
+ attr_reader :charge_to_sales_receipts
6
+ attr_reader :refund_to_refund_receipts
7
+ attr_reader :deposit
8
+
9
+ def initialize(transfer, configuration)
10
+ super(configuration)
11
+
12
+ @transfer = transfer
13
+
14
+ charges = stripe_api.get_transfer_charges(transfer.id)
15
+ @charge_to_sales_receipts = charges.map {|charge| ChargeToSalesReceipt.new(charge, configuration) }
16
+
17
+ refunds = stripe_api.get_transfer_refunds(transfer.id)
18
+ @refund_to_refund_receipts = refunds.map {|refund| RefundToRefundReceipt.new(refund, configuration) }
19
+
20
+ @deposit = build_empty_deposit
21
+ end
22
+
23
+ def find
24
+ return @found if @found
25
+
26
+ # Quickbooks doesn't have an external ID on Deposits, so we have to
27
+ # search for a match on private_note...
28
+ # first narrow down results by txn_date
29
+ deposits = quickbooks_api.deposit_service.find_by(:txn_date, format_date(transfer.created))
30
+ deposits.each do |deposit|
31
+ return @found = deposit if deposit.private_note =~ /#{transfer.id}/
32
+ end
33
+
34
+ nil
35
+ end
36
+
37
+ def create!
38
+ raise "Deposit for #{transfer.id} already exists: #{find.id}" if exists?
39
+
40
+ create_dependencies!
41
+ build_deposit_line_items
42
+ quickbooks_api.deposit_service.create(deposit)
43
+ end
44
+
45
+ def create_dependencies!
46
+ charge_to_sales_receipts.each do |charge_to_sales_receipt|
47
+ charge_to_sales_receipt.find_or_create
48
+ end
49
+
50
+ refund_to_refund_receipts.each do |refund_to_refund_receipt|
51
+ refund_to_refund_receipt.find_or_create
52
+ end
53
+ end
54
+
55
+ def delete!
56
+ return false unless exists?
57
+
58
+ result = quickbooks_api.deposit_service.delete(find)
59
+ @found = nil
60
+
61
+ delete_dependencies!
62
+
63
+ result
64
+ end
65
+
66
+ def delete_dependencies!
67
+ charge_to_sales_receipts.each do |charge_to_sales_receipt|
68
+ charge_to_sales_receipt.delete! if charge_to_sales_receipt.exists?
69
+ end
70
+
71
+ refund_to_refund_receipts.each do |refund_to_refund_receipt|
72
+ refund_to_refund_receipt.delete! if refund_to_refund_receipt.exists?
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def build_empty_deposit
79
+ deposit = Quickbooks::Model::Deposit.new
80
+ deposit.deposit_to_account_id = quickbooks_api.deposit_account.id
81
+ deposit.txn_date = format_date(transfer.created)
82
+ deposit.private_note = "Imported by Stripe2QB: #{transfer.id}"
83
+ deposit.auto_doc_number!
84
+
85
+ deposit
86
+ end
87
+
88
+ def build_deposit_line_items
89
+ charge_to_sales_receipts.each do |charge_to_sales_receipt|
90
+ deposit.line_items << build_deposit_line_item_for_sales_receipt(charge_to_sales_receipt.find)
91
+ end
92
+
93
+ refund_to_refund_receipts.each do |refund_to_refund_receipt|
94
+ deposit.line_items << build_deposit_line_item_for_refund_receipt(refund_to_refund_receipt.find)
95
+ end
96
+
97
+ deposit.line_items << build_deposit_line_item_for_fees
98
+ deposit.line_items << build_deposit_line_item_for_fee_refunds
99
+ end
100
+
101
+ def build_deposit_line_item_for_sales_receipt(sales_receipt)
102
+ line_item = Quickbooks::Model::DepositLineItem.new
103
+ line_item.amount = sales_receipt.total
104
+ line_item.description = "Deposit for SalesReceipt #{sales_receipt.id}"
105
+
106
+ linked_txn = Quickbooks::Model::LinkedTransaction.new
107
+ linked_txn.txn_id = sales_receipt.id
108
+ linked_txn.txn_type = 'SalesReceipt'
109
+ linked_txn.txn_line_id = 0
110
+ line_item.linked_transactions = [ linked_txn ]
111
+
112
+ line_item
113
+ end
114
+
115
+ def build_deposit_line_item_for_refund_receipt(refund_receipt)
116
+ line_item = Quickbooks::Model::DepositLineItem.new
117
+ line_item.amount = -1 * refund_receipt.total # convert to negative number for Deposit
118
+ line_item.description = "Deposit for RefundReceipt #{refund_receipt.id}"
119
+
120
+ linked_txn = Quickbooks::Model::LinkedTransaction.new
121
+ linked_txn.txn_id = refund_receipt.id
122
+ linked_txn.txn_type = 'RefundReceipt'
123
+ linked_txn.txn_line_id = 0
124
+ line_item.linked_transactions = [ linked_txn ]
125
+
126
+ line_item
127
+ end
128
+
129
+ def build_deposit_line_item_for_fees
130
+ line_item = Quickbooks::Model::DepositLineItem.new
131
+ fees = charge_to_sales_receipts.inject(0) {|sum, charge_to_sales_receipts| sum += charge_to_sales_receipts.charge.fee }
132
+ line_item.amount = format_amount(-1 * fees) # convert to negative number for Deposit
133
+ line_item.description = "Stripe Fees for #{transfer.id}"
134
+ set_deposit_line_detail_for_fees(line_item)
135
+
136
+ line_item
137
+ end
138
+
139
+ def build_deposit_line_item_for_fee_refunds
140
+ line_item = Quickbooks::Model::DepositLineItem.new
141
+ fee_refunds = refund_to_refund_receipts.inject(0) {|sum, refund_to_refund_receipt| sum += refund_to_refund_receipt.refund.fee }
142
+ line_item.amount = format_amount(-1 * fee_refunds) # convert to positive number for Deposit
143
+ line_item.description = "Stripe Fee Refunds for #{transfer.id}"
144
+ set_deposit_line_detail_for_fees(line_item)
145
+
146
+ line_item
147
+ end
148
+
149
+ def set_deposit_line_detail_for_fees(line_item)
150
+ line_item.deposit_line_detail! do |detail|
151
+ detail.entity = Quickbooks::Model::BaseReference.new(quickbooks_api.deposit_fees_vendor.id, type: 'Vendor')
152
+ detail.account_id = quickbooks_api.deposit_fees_account.id
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,7 @@
1
+ require 'stripe2qb/converters/base'
2
+ require 'stripe2qb/converters/charge_to_sales_receipt'
3
+ require 'stripe2qb/converters/refund_to_refund_receipt'
4
+ require 'stripe2qb/converters/transfer_to_deposit'
5
+
6
+ module Converters
7
+ end
@@ -0,0 +1,12 @@
1
+ module Stripe2QB
2
+ module OptionsReading
3
+ def set_attribute_from_options(key, options)
4
+ value = get_required_from_options(key, options)
5
+ instance_variable_set("@#{key}", value)
6
+ end
7
+
8
+ def get_required_from_options(key, options)
9
+ options[key] or raise ConfigurationError.new("missing #{key}")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ require 'oauth'
2
+
3
+ module Stripe2QB
4
+ class QuickbooksApi
5
+ class AccessToken
6
+ include OptionsReading
7
+
8
+ attr_reader :api_access_token
9
+ attr_reader :api_access_secret
10
+ attr_reader :oauth_consumer_key
11
+ attr_reader :oauth_consumer_secret
12
+
13
+ attr_reader :oauth
14
+
15
+ def initialize(options = {})
16
+ set_attribute_from_options('api_access_token', options)
17
+ set_attribute_from_options('api_access_secret', options)
18
+ set_attribute_from_options('oauth_consumer_key', options)
19
+ set_attribute_from_options('oauth_consumer_secret', options)
20
+
21
+ oauth_consumer = ::OAuth::Consumer.new(
22
+ oauth_consumer_key,
23
+ oauth_consumer_secret,
24
+ {
25
+ site: 'https://oauth.intuit.com',
26
+ request_token_path: '/oauth/v1/get_request_token',
27
+ authorize_url: 'https://appcenter.intuit.com/Connect/Begin',
28
+ access_token_path: '/oauth/v1/get_access_token'
29
+ }
30
+ )
31
+
32
+ @oauth = ::OAuth::AccessToken.new(oauth_consumer, api_access_token, api_access_secret)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,68 @@
1
+ require 'stripe2qb/quickbooks_api/access_token'
2
+ require 'active_support/inflector'
3
+ require 'quickbooks-ruby'
4
+
5
+ module Stripe2QB
6
+ class QuickbooksApi
7
+ include OptionsReading
8
+ include ActiveSupport::Inflector
9
+
10
+ attr_reader :realm_id
11
+ attr_reader :access_token
12
+ attr_reader :receipt_customer
13
+ attr_reader :receipt_item
14
+ attr_reader :receipt_payment_method
15
+ attr_reader :receipt_account
16
+ attr_reader :deposit_account
17
+ attr_reader :deposit_fees_vendor
18
+ attr_reader :deposit_fees_account
19
+
20
+ def initialize(options)
21
+ set_attribute_from_options('realm_id', options)
22
+ @access_token = AccessToken.new(options)
23
+ @receipt_customer = find_by_name_or_id(:customer, 'receipt_customer', options, :display_name)
24
+ @receipt_item = find_by_name_or_id(:item, 'receipt_item', options)
25
+ @receipt_payment_method = find_by_name_or_id(:payment_method, 'receipt_payment_method', options)
26
+ @receipt_account = find_by_name_or_id(:account, 'receipt_account', options)
27
+ @deposit_account = find_by_name_or_id(:account, 'deposit_account', options)
28
+ @deposit_fees_vendor = find_by_name_or_id(:vendor, 'deposit_fees_vendor', options, :display_name)
29
+ @deposit_fees_account = find_by_name_or_id(:account, 'deposit_fees_account', options)
30
+ end
31
+
32
+ def renew_access_token
33
+ result = access_token_service.renew
34
+
35
+ { token: result.token, secret: result.secret, created_on: Date.today }
36
+ end
37
+
38
+ def method_missing(symbol, *args)
39
+ if symbol.to_s =~ /^(.+)_service$/
40
+ get_service($1.camelize)
41
+ else
42
+ super
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def find_by_name_or_id(type, key, options, name_attr = :name)
49
+ value = get_required_from_options(key, options)
50
+ service = send("#{type}_service")
51
+
52
+ unless object = value.is_a?(String) ? service.find_by(name_attr, value).first : service.find_by(:id, value).first
53
+ msg = value.is_a?(String) ? "#{name_attr} '#{value}'" : "ID #{value}"
54
+ raise ConfigurationError.new("no #{type} for #{msg}")
55
+ end
56
+
57
+ object
58
+ end
59
+
60
+ def get_service(name)
61
+ service = "Quickbooks::Service::#{name}".constantize.new
62
+ service.access_token = access_token.oauth
63
+ service.company_id = realm_id
64
+
65
+ service
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,40 @@
1
+ require 'stripe'
2
+
3
+ module Stripe2QB
4
+ class StripeApi
5
+ include OptionsReading
6
+
7
+ attr_reader :api_key
8
+
9
+ def initialize(options)
10
+ Stripe.api_key = set_attribute_from_options('api_key', options)
11
+ end
12
+
13
+ def get_transfers(start_date, end_date = nil)
14
+ Stripe::Transfer.list(date: date_range_criteria(start_date, end_date), status: 'paid', limit: 100)
15
+ end
16
+
17
+ def get_transfer_charges(transfer_id)
18
+ Stripe::BalanceTransaction.all(transfer: transfer_id, type: 'charge', limit: 100)
19
+ end
20
+
21
+ def get_transfer_refunds(transfer_id)
22
+ Stripe::BalanceTransaction.all(transfer: transfer_id, type: 'refund', limit: 100)
23
+ end
24
+
25
+ private
26
+
27
+ def date_range_criteria(start_date, end_date = nil)
28
+ start_time = to_time(start_date)
29
+ end_time = to_time(end_date || Date.parse(start_date) + 1.day)
30
+
31
+ { gte: start_time, lt: end_time }
32
+ end
33
+
34
+ def to_time(date)
35
+ date = Date.parse(date) if date.is_a?(String)
36
+
37
+ Time.parse("#{date} 0:00 UTC").to_i
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Stripe2QB
2
+ VERSION = "0.1.0"
3
+ end
data/lib/stripe2qb.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'stripe2qb/configuration'
2
+ require 'stripe2qb/converters'
3
+ require 'stripe2qb/quickbooks_api'
4
+ require 'stripe2qb/stripe_api'
5
+ require 'stripe2qb/version'
6
+
7
+ module Stripe2QB
8
+ def self.new(options = 'config/stripe2qb.yml')
9
+ Configuration.new(options)
10
+ end
11
+ end
data/stripe2qb.gemspec ADDED
@@ -0,0 +1,36 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stripe2qb/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "stripe2qb"
8
+ spec.version = Stripe2QB::VERSION
9
+ spec.authors = ["Anthony Wang"]
10
+ spec.email = ["anthony@anthonywang.com"]
11
+
12
+ spec.summary = 'Import Stripe transactions into Quickbooks'
13
+ spec.description = 'Import Stripe transactions into Quickbooks. Relies on the Stripe and Quickbooks gems.'
14
+ spec.homepage = "http://rubygems.org/gems/stripe2qb"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.11"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+
34
+ spec.add_runtime_dependency 'quickbooks-ruby', '~> 0.4'
35
+ spec.add_runtime_dependency 'activesupport'
36
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stripe2qb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Anthony Wang
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-10-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: quickbooks-ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: Import Stripe transactions into Quickbooks. Relies on the Stripe and
84
+ Quickbooks gems.
85
+ email:
86
+ - anthony@anthonywang.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".rspec"
93
+ - ".travis.yml"
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - config/.keep
102
+ - config/stripe2qb.yml
103
+ - lib/stripe2qb.rb
104
+ - lib/stripe2qb/configuration.rb
105
+ - lib/stripe2qb/converters.rb
106
+ - lib/stripe2qb/converters/base.rb
107
+ - lib/stripe2qb/converters/charge_to_sales_receipt.rb
108
+ - lib/stripe2qb/converters/refund_to_refund_receipt.rb
109
+ - lib/stripe2qb/converters/transfer_to_deposit.rb
110
+ - lib/stripe2qb/options_reading.rb
111
+ - lib/stripe2qb/quickbooks_api.rb
112
+ - lib/stripe2qb/quickbooks_api/access_token.rb
113
+ - lib/stripe2qb/stripe_api.rb
114
+ - lib/stripe2qb/version.rb
115
+ - stripe2qb.gemspec
116
+ homepage: http://rubygems.org/gems/stripe2qb
117
+ licenses:
118
+ - MIT
119
+ metadata:
120
+ allowed_push_host: https://rubygems.org
121
+ post_install_message:
122
+ rdoc_options: []
123
+ require_paths:
124
+ - lib
125
+ required_ruby_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ">="
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 2.5.1
138
+ signing_key:
139
+ specification_version: 4
140
+ summary: Import Stripe transactions into Quickbooks
141
+ test_files: []