shopify-gold 2.0.0.pre

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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +144 -0
  3. data/Rakefile +32 -0
  4. data/app/assets/config/gold_manifest.js +0 -0
  5. data/app/assets/images/gold/app-logo.svg +17 -0
  6. data/app/assets/images/gold/aside-bg.svg +54 -0
  7. data/app/assets/javascripts/gold/billing.js +30 -0
  8. data/app/assets/stylesheets/gold/billing.sass +64 -0
  9. data/app/assets/stylesheets/shopify/polaris.css +1 -0
  10. data/app/controllers/gold/admin/billing_controller.rb +190 -0
  11. data/app/controllers/gold/application_controller.rb +17 -0
  12. data/app/controllers/gold/authenticated_controller.rb +15 -0
  13. data/app/controllers/gold/billing_controller.rb +185 -0
  14. data/app/controllers/gold/concerns/merchant_facing.rb +85 -0
  15. data/app/jobs/app_uninstalled_job.rb +2 -0
  16. data/app/jobs/gold/after_authenticate_job.rb +36 -0
  17. data/app/jobs/gold/app_uninstalled_job.rb +10 -0
  18. data/app/jobs/gold/application_job.rb +20 -0
  19. data/app/mailers/gold/application_mailer.rb +7 -0
  20. data/app/mailers/gold/billing_mailer.rb +36 -0
  21. data/app/models/gold/billing.rb +157 -0
  22. data/app/models/gold/concerns/gilded.rb +22 -0
  23. data/app/models/gold/machine.rb +301 -0
  24. data/app/models/gold/shopify_plan.rb +70 -0
  25. data/app/models/gold/tier.rb +97 -0
  26. data/app/models/gold/transition.rb +26 -0
  27. data/app/operations/gold/accept_or_decline_charge_op.rb +119 -0
  28. data/app/operations/gold/accept_or_decline_terms_op.rb +26 -0
  29. data/app/operations/gold/apply_discount_op.rb +32 -0
  30. data/app/operations/gold/apply_tier_op.rb +51 -0
  31. data/app/operations/gold/charge_op.rb +99 -0
  32. data/app/operations/gold/check_charge_op.rb +50 -0
  33. data/app/operations/gold/cleanup_op.rb +20 -0
  34. data/app/operations/gold/convert_affiliate_to_paid_op.rb +32 -0
  35. data/app/operations/gold/freeze_op.rb +15 -0
  36. data/app/operations/gold/install_op.rb +30 -0
  37. data/app/operations/gold/issue_credit_op.rb +55 -0
  38. data/app/operations/gold/mark_as_delinquent_op.rb +21 -0
  39. data/app/operations/gold/resolve_outstanding_charge_op.rb +90 -0
  40. data/app/operations/gold/select_tier_op.rb +58 -0
  41. data/app/operations/gold/suspend_op.rb +21 -0
  42. data/app/operations/gold/uninstall_op.rb +20 -0
  43. data/app/operations/gold/unsuspend_op.rb +20 -0
  44. data/app/views/gold/admin/billing/_active_charge.erb +29 -0
  45. data/app/views/gold/admin/billing/_credit.erb +46 -0
  46. data/app/views/gold/admin/billing/_discount.erb +23 -0
  47. data/app/views/gold/admin/billing/_history.erb +65 -0
  48. data/app/views/gold/admin/billing/_overview.erb +14 -0
  49. data/app/views/gold/admin/billing/_shopify_plan.erb +21 -0
  50. data/app/views/gold/admin/billing/_state.erb +26 -0
  51. data/app/views/gold/admin/billing/_status.erb +25 -0
  52. data/app/views/gold/admin/billing/_tier.erb +155 -0
  53. data/app/views/gold/admin/billing/_trial_days.erb +42 -0
  54. data/app/views/gold/billing/_inner_head.html.erb +1 -0
  55. data/app/views/gold/billing/declined_charge.html.erb +20 -0
  56. data/app/views/gold/billing/expired_charge.html.erb +14 -0
  57. data/app/views/gold/billing/missing_charge.html.erb +22 -0
  58. data/app/views/gold/billing/outstanding_charge.html.erb +12 -0
  59. data/app/views/gold/billing/suspended.html.erb +8 -0
  60. data/app/views/gold/billing/terms.html.erb +22 -0
  61. data/app/views/gold/billing/tier.html.erb +29 -0
  62. data/app/views/gold/billing/transition_error.html.erb +9 -0
  63. data/app/views/gold/billing/unavailable.erb +8 -0
  64. data/app/views/gold/billing/uninstalled.html.erb +13 -0
  65. data/app/views/gold/billing_mailer/affiliate_to_paid.erb +23 -0
  66. data/app/views/gold/billing_mailer/delinquent.html.erb +21 -0
  67. data/app/views/gold/billing_mailer/suspension.html.erb +19 -0
  68. data/app/views/layouts/gold/billing.html.erb +22 -0
  69. data/app/views/layouts/gold/mailer.html.erb +13 -0
  70. data/app/views/layouts/gold/mailer.text.erb +1 -0
  71. data/config/routes.rb +33 -0
  72. data/db/migrate/01_create_gold_billing.rb +11 -0
  73. data/db/migrate/02_create_gold_transitions.rb +29 -0
  74. data/db/migrate/03_add_foreign_key_to_billing.rb +8 -0
  75. data/lib/gold/admin_engine.rb +8 -0
  76. data/lib/gold/billing_migrator.rb +107 -0
  77. data/lib/gold/coverage.rb +89 -0
  78. data/lib/gold/diagram.rb +40 -0
  79. data/lib/gold/engine.rb +16 -0
  80. data/lib/gold/exceptions/metadata_missing.rb +5 -0
  81. data/lib/gold/outcomes.rb +77 -0
  82. data/lib/gold/retries.rb +31 -0
  83. data/lib/gold/version.rb +3 -0
  84. data/lib/gold.rb +102 -0
  85. data/lib/tasks/gold_tasks.rake +94 -0
  86. metadata +298 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 917c31cd7ce6e41b8147461e53ce5a5568a771fdde314e13d566235fdb67a052
4
+ data.tar.gz: ebab84d89085345240df8395b224eec8c51e6d9d7b89134354e57ad6865c13ad
5
+ SHA512:
6
+ metadata.gz: 93f021916913a6a29e9e3cd7a94fbecf6349fee1b84f4ef9cd1ff798a52c13041a01eb8fc98dc59f166f9e76b05686bd2895855c5cf795e8a4c39c523e1c03fe
7
+ data.tar.gz: 64c4a74b23f203225264243a5bc6a74268fb05f420527d09be71b794a71f2af85cbe47335e8528e3d539fe342ef96061c585b742c1d5cea5d23620f512cb051f
data/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # Gold
2
+ Gold is Helium’s approach to predictably and accurately billing merchants for
3
+ Shopify apps.
4
+
5
+ For more information about the goals and design of this project, please review
6
+ [Gold's design document](https://docs.google.com/document/d/1U5REQyTDhXkI6q8VJYQgPDaAbiJMq3UAp3G0c6U8F2A/view).
7
+
8
+ ## Tour
9
+ Gold is comprised of a few pieces:
10
+
11
+ * `Gold::BillingController` allows merchants to select tiers, set up charges,
12
+ and review their billing information.
13
+ * `Gold::Billing` ties together all pieces of a shop's billing information.
14
+ * `Gold::Machine` defines the state that a shop is in and what transitions
15
+ can be made from that state to others.
16
+ * `Gold::Transition` records these state transitions in the database.
17
+ * `Gold::Tier` holds definitions of tiers that are available for merchants to
18
+ select from. Apps using Gold define these in their `config/tiers.yml` file.
19
+ * `Gold::Concerns::Gilded` is a mixin to an app's shop class to give it Gold's
20
+ functionality. This app-specific shop class is configured with the `Gold`
21
+ module's `#shop_class=` setter.
22
+
23
+
24
+ ## Installation
25
+ Add this line to your application's Gemfile:
26
+
27
+ ```ruby
28
+ gem 'gold'
29
+ ```
30
+
31
+ And then execute:
32
+
33
+ ```bash
34
+ $ bundle
35
+ ```
36
+
37
+ You will need to apply migrations to your app's `Shop` class and bring in the
38
+ other Gold models to your database:
39
+
40
+ ```bash
41
+ $ bin/rails gold:install:migrations
42
+ $ bin/rails db:migrate
43
+ ```
44
+
45
+ Finally, you will need to mount the engine in your `config/routes.rb` file:
46
+
47
+ ```ruby
48
+ mount Gold::Engine, at: "/"
49
+ ```
50
+
51
+ Note that by default, the engine's name is `gold_engine`. If you must change this,
52
+ you'll need to define a new helper method `gold_engine` and reference your custom
53
+ `as` param.
54
+
55
+ ## Configuration
56
+ Add `gold.rb` within the `initializers` folder of your project. Below are Gold's
57
+ configuration options:
58
+
59
+ ```
60
+ Gold.configure do |config|
61
+ # The class name (string) of the the app's Active Record Shop model
62
+ config.shop_class = "Shop"
63
+
64
+ # The attribute of the *.myshopify.com domain on the Shop model
65
+ config.shop_domain_attribute = "shopify_domain"
66
+
67
+ # Should Gold create test charges? Helpful in a development environment
68
+ config.test_charge = false
69
+
70
+ # The logger Gold will use
71
+ config.logger = Logger.new(STDOUT)
72
+
73
+ # How many days we'll wait before cleaning up an uninstalled shop
74
+ config.days_until_cleanup = 30
75
+
76
+ # How many days we'll wait before we uninstall a shop without payment
77
+ config.days_until_delinquent = 7
78
+
79
+ # The name of the app using gold (used in the charge name)
80
+ config.app_name = "My App"
81
+
82
+ # The email Gold uses to send emails from
83
+ config.contact_email = "example@heliumdev.com"
84
+
85
+ # The URL to a plan comparison page for the app
86
+ config.plan_comparison_url = "https://heliumdev.com/pricing"
87
+
88
+ # Login credential's to Gold's backend
89
+ config.admin_credentials = {
90
+ user: "admin",
91
+ password: "password123"
92
+ }
93
+
94
+ # The API version used by Shopify (https://help.shopify.com/en/api/versioning)
95
+ @shopify_api_version = "2019-04"
96
+
97
+ # If Gold is allowed to cancel charges (paid -> free) automatically
98
+ @allow_automated_charge_cancellation = true
99
+ end
100
+ ```
101
+
102
+ ## Hooks
103
+ You can hook into Gold's lifecycle by providing a Proc. Available hooks:
104
+ ```
105
+ Gold.configure do |config|
106
+ config.on_install = proc {
107
+ puts "Installing Gold..."
108
+ }
109
+
110
+ config.on_terms = proc {
111
+ puts "Accepted terms from Gold..."
112
+ }
113
+
114
+ config.on_apply_tier = proc { |billing, tier_id|
115
+ puts "Applied tier..."
116
+ }
117
+
118
+ config.on_uninstall = proc {
119
+ puts "Uninstalling Gold..."
120
+ }
121
+
122
+ config.on_cleanup = proc { |billing|
123
+ puts "Cleaning up shop"
124
+ }
125
+ end
126
+ ```
127
+
128
+ ## Contributing
129
+
130
+ Run tests with:
131
+
132
+ ```bash
133
+ $ bin/rails test
134
+ ```
135
+
136
+ Run lint/style checks with:
137
+
138
+ ```bash
139
+ $ bundle exec rubocop
140
+ ```
141
+
142
+ If you want to see a visual representation of `Gold::Machine`, you can run
143
+ `bin/rails app:gold:diagram`. You will need to have the Graphviz tool installed,
144
+ which you can do on Mac via Homebrew with `brew install graphviz`
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require "bundler/setup"
3
+ rescue LoadError
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
+ end
6
+
7
+ require "rdoc/task"
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = "rdoc"
11
+ rdoc.title = "Gold"
12
+ rdoc.options << "--line-numbers"
13
+ rdoc.rdoc_files.include("README.md")
14
+ rdoc.rdoc_files.include("lib/**/*.rb")
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load "rails/tasks/engine.rake"
19
+
20
+ load "rails/tasks/statistics.rake"
21
+
22
+ require "bundler/gem_tasks"
23
+
24
+ require "rake/testtask"
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << "test"
28
+ t.pattern = "test/**/*_test.rb"
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
File without changes
@@ -0,0 +1,17 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg width="164px" height="158px" viewBox="0 0 164 158" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
3
+ <!-- Generator: Sketch 55.2 (78181) - https://sketchapp.com -->
4
+ <title>Logo</title>
5
+ <desc>Created with Sketch.</desc>
6
+ <defs>
7
+ <linearGradient x1="28.9979993%" y1="-8.96517743%" x2="84.8882717%" y2="105.103572%" id="linearGradient-1">
8
+ <stop stop-color="#5801E3" offset="0%"></stop>
9
+ <stop stop-color="#FC4646" offset="100%"></stop>
10
+ </linearGradient>
11
+ </defs>
12
+ <g id="Billing" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
13
+ <g id="Intro-form" fill="url(#linearGradient-1)">
14
+ <path d="M-21,-12.5035429 L-21,-13.2617143 C-20.8582857,-22.9532 -15.2800571,-28 -5.47874286,-28 C17.0963429,-27.9964571 62.0693714,-1.21777143 114.562114,57.2801143 C168.3604,-5.90851429 178.496514,-52.1676 88.2882857,15.6285143 C88.1235429,15.4850286 80.8181714,9.04765714 80.6463429,8.89885714 C82.2530286,7.80765714 124.276629,-27.9256 149.267943,-27.9256 C177.9084,-27.9256 166.782057,11.8128571 121.291771,64.9716571 C197.069943,153.530686 162.750286,187.504914 75.224,116.444057 L72.4216,114.171314 C59.5840571,124.334 20.6622286,158 -4.79142857,158 C-25.6978286,158 -23.1257143,136.420457 -13.1649714,116.555657 C-5.75862857,101.787257 6.44297143,84.4414286 23.1192,64.9716571 C-7.56194286,29.1232571 -20.7821143,2.80337143 -21,-12.5035429 Z M64.6857714,22.6929714 C-17.4925714,-47.5725143 -37.7949143,-21.9984 29.8417714,57.312 C42.1532,43.8172571 56.6523429,30.3862857 64.6857714,22.6929714 Z M65.9665143,94.9389143 C65.9665143,94.9389143 58.5761143,101.626057 58.3918857,101.789029 C57.1306286,100.549029 44.6438286,89.1622857 29.8311429,72.6561143 C14.6074857,90.5050286 -27.0405714,148.000286 -4.82154286,148.000286 C21.2556571,148.000286 78.1185143,98.2692 107.846629,64.9876 C97.1737714,53.0180571 84.8162857,40.6056571 72.3542857,29.4137714 C62.2376571,38.9830286 47.2248,53.2377143 36.5608,64.9645714 C44.3710286,73.5046286 57.4902286,86.5848571 65.9665143,94.9389143 Z M80.0493714,107.161771 C80.2247429,107.291086 122.165086,144.712514 145.4824,144.712514 C170.225714,144.712514 130.703371,91.5554857 114.565657,72.6738286 C104.902514,83.4352571 94.4971429,94.0709143 80.0493714,107.161771 Z" id="Logo"></path>
15
+ </g>
16
+ </g>
17
+ </svg>
@@ -0,0 +1,54 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!-- Generator: Adobe Illustrator 23.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
3
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
4
+ viewBox="0 0 656 672" style="enable-background:new 0 0 656 672;" xml:space="preserve">
5
+ <style type="text/css">
6
+ .st0{filter:url(#Adobe_OpacityMaskFilter);}
7
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
8
+ .st2{mask:url(#mask-2_1_);fill:none;stroke:url(#Path-8_1_);}
9
+ .st3{filter:url(#Adobe_OpacityMaskFilter_1_);}
10
+ .st4{mask:url(#mask-2_2_);fill:none;stroke:url(#Path-9_1_);}
11
+ </style>
12
+ <title>Path 8 + Path 9 Mask</title>
13
+ <desc>Created with Sketch.</desc>
14
+ <g id="Billing">
15
+ <g id="Intro-form" transform="translate(-720.000000, -173.000000)">
16
+ <g transform="translate(720.000000, 143.000000)">
17
+ <g id="Mask">
18
+ </g>
19
+ <defs>
20
+ <filter id="Adobe_OpacityMaskFilter" filterUnits="userSpaceOnUse" x="-3.5" y="47.3" width="278.5" height="654.4">
21
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
22
+ </filter>
23
+ </defs>
24
+ <mask maskUnits="userSpaceOnUse" x="-3.5" y="47.3" width="278.5" height="654.4" id="mask-2_1_">
25
+ <g class="st0">
26
+ <rect id="path-1_1_" class="st1" width="668" height="735"/>
27
+ </g>
28
+ </mask>
29
+
30
+ <linearGradient id="Path-8_1_" gradientUnits="userSpaceOnUse" x1="-354.1977" y1="748.4078" x2="-353.0956" y2="748.4737" gradientTransform="matrix(-188.4878 -657.335 -167.9035 48.1456 59202.0664 -268195.8125)">
31
+ <stop offset="0" style="stop-color:#5801E3"/>
32
+ <stop offset="1" style="stop-color:#42E59A"/>
33
+ </linearGradient>
34
+ <path id="Path-8" class="st2" d="M197.9,701.4C346.2,421.8,279.1,203.9-3.3,47.7"/>
35
+ <defs>
36
+ <filter id="Adobe_OpacityMaskFilter_1_" filterUnits="userSpaceOnUse" x="-3.4" y="24.1" width="659.1" height="526.6">
37
+ <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0"/>
38
+ </filter>
39
+ </defs>
40
+ <mask maskUnits="userSpaceOnUse" x="-3.4" y="24.1" width="659.1" height="526.6" id="mask-2_2_">
41
+ <g class="st3">
42
+ <rect id="path-1_2_" class="st1" width="668" height="735"/>
43
+ </g>
44
+ </mask>
45
+
46
+ <linearGradient id="Path-9_1_" gradientUnits="userSpaceOnUse" x1="-353.4046" y1="750.621" x2="-353.7825" y2="749.4943" gradientTransform="matrix(65.1755 -411.5021 -723.7028 -114.6233 566150.875 -59200.8633)">
47
+ <stop offset="0" style="stop-color:#5801E3"/>
48
+ <stop offset="1" style="stop-color:#FC4646"/>
49
+ </linearGradient>
50
+ <path id="Path-9" class="st4" d="M-2.9,24.3c120.7,306.3,340.2,481.6,658.6,525.9"/>
51
+ </g>
52
+ </g>
53
+ </g>
54
+ </svg>
@@ -0,0 +1,30 @@
1
+ var $tierForm = document.querySelector('.tiers form');
2
+
3
+ function tierStep($form) {
4
+ var $tiers = $form.querySelectorAll('input[name="tier"]');
5
+
6
+ function setSelectPlanButtonState() {
7
+ var $selectedTier = $form.querySelector('input[name="tier"]:checked'),
8
+ $submit = $form.querySelector('button[type="submit"]');
9
+
10
+ if ($selectedTier) {
11
+ $submit.removeAttribute('disabled');
12
+ $submit.classList.remove('Polaris-Button--disabled');
13
+ } else {
14
+ $submit.setAttribute('disabled', 'disabled');
15
+ $submit.classList.add('Polaris-Button--disabled');
16
+ }
17
+ }
18
+
19
+ for (var i = 0; i < $tiers.length; i++) {
20
+ $tiers[i].addEventListener('change', function(e) {
21
+ setSelectPlanButtonState();
22
+ });
23
+ }
24
+
25
+ setSelectPlanButtonState();
26
+ }
27
+
28
+ if ($tierForm) {
29
+ tierStep($tierForm);
30
+ }
@@ -0,0 +1,64 @@
1
+ body
2
+ display: flex
3
+ align-items: stretch
4
+ margin: 0
5
+ padding: 0
6
+
7
+ main
8
+ min-height: 100%
9
+ width: 50%
10
+ padding: 180px 25px 25px 25px
11
+ background-color: white
12
+ position: relative
13
+ overflow: auto
14
+
15
+ &:before
16
+ content: asset-url('gold/app-logo.svg')
17
+ position: absolute
18
+ top: 0
19
+ left: 0
20
+ opacity: 0.2
21
+ width: 175px
22
+
23
+ .main-content
24
+ margin: auto
25
+ max-width: 600px
26
+
27
+ aside
28
+ width: 50%
29
+ background: #160A2C
30
+ background-image: asset-url('gold/aside-bg.svg')
31
+ background-repeat: no-repeat
32
+ background-position: 0 40%
33
+ background-size: 700px
34
+
35
+ .tiers
36
+ .tier
37
+ border-top: 1px solid #DFE3E8
38
+ padding: 15px 0 5px 0
39
+ cursor: pointer
40
+ background: white
41
+ position: relative
42
+
43
+ &[data-disabled]
44
+ .tier-heading
45
+ opacity: 0.5
46
+
47
+ &:first-of-type
48
+ border-top: none
49
+
50
+ .Polaris-Choice
51
+ width: 100%
52
+ align-items: center
53
+
54
+ .Polaris-Choice__Control
55
+ margin-right: 15px
56
+
57
+ .Polaris-Choice__Label
58
+ display: flex
59
+ align-items: center
60
+ justify-content: space-between
61
+ width: 100%
62
+
63
+ .tier-description
64
+ max-width: 320px