shopify-gold 2.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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