bullet_train 1.0.83 → 1.0.85

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d2119a17f5ed1aace492642ab674c8fb67cadefd29100aee3809e590813da3bb
4
- data.tar.gz: '0679c15de2ec6260be9beab3c1261fbef4892cacd961d285f813229aac5b1616'
3
+ metadata.gz: c213e551929a0653ebc08d6ac97a7c438ffa69123842c19a346bf4639f22bc1b
4
+ data.tar.gz: db2d5b92aafd6157a9f6856a9394b83871636ad43bc5d7d1fac600209c772213
5
5
  SHA512:
6
- metadata.gz: 807684f8b6d5a99a6d35a78d0e50cbeb8a0ef40d06b1e56c1c653a6851ea0289e50c75f2a1e75a1500f6799f3fcd91a603fffaa64ea12324068a851290e67186
7
- data.tar.gz: 964b44f5ddaa872e26e8cefcf0dd6f9a11d19fc7d3b435e10e7a14d6f62c420a60945dde1eb0c9c588f049cd64423e93c82c52e2b207c01c2cfbe7d58c5b125b
6
+ metadata.gz: 07bfc1c4f89999d5d9bd188a8caecba3a6d8af8a727baaef12d952e2732f06a181a1f5a2434aee88dfafce9f4e7286b6891477273ad84de5858628cafadfc484
7
+ data.tar.gz: f884952e780148f47b6589713ead7395c730ff1ecbd2f1b883176bbcc9470165272da2c16548193486750f06eb351da557d2694ba3e8098a0dd834b6b0a098df
@@ -0,0 +1,2 @@
1
+ import{Controller as e}from"@hotwired/stimulus";function t(e){const t=(e.match(/^(?:\.\/)?(.+)(?:[_-]controller\..+?)$/)||[])[1];if(t)return t.replace(/_/g,"-").replace(/\//g,"--")}class i extends e{copy(){this.inputTarget.value=this.sourceTarget.innerText,this.inputTarget.select(),document.execCommand("copy"),this.buttonTarget.innerHTML='<i id="copied" class="fas fa-check w-4 h-4 block text-green-600"></i>',setTimeout(function(){document.getElementById("copied").innerHTML='<i class="far fa-copy w-4 h-4 block text-gray-600"></i>'},1500)}}i.targets=["source","input","button"];class r extends e{constructor(){super(...arguments),this.removeTrailingNewlines=e=>{e.element.innerHTML.match(/<br><\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-10)+"</div>",this.removeTrailingNewlines(e))},this.removeTrailingWhitespace=e=>{e.element.innerHTML.match(/&nbsp;<\/div>$/)?(e.element.innerHTML=e.element.innerHTML.slice(0,-12)+"</div>",this.removeTrailingWhitespace(e)):e.element.innerHTML.match(/&nbsp; <\/div>$/)&&(e.element.innerHTML=e.element.innerHTML.slice(0,-13)+"</div>",this.removeTrailingWhitespace(e))}}resetOnSuccess(e){e.detail.success&&e.target.reset()}stripTrix(){this.trixFieldTargets.forEach(e=>{this.removeTrailingNewlines(e.editor),this.removeTrailingWhitespace(e.editor),e.parentElement.querySelector("input").value=e.innerHTML})}submitOnReturn(e){if((e.metaKey||e.ctrlKey)&&13==e.keyCode){e.preventDefault();let t=e.target.closest("form");this.submitForm(t)}}submitForm(e){e.requestSubmit?e.requestSubmit():e.querySelector("[type=submit]").click()}}r.targets=["trixField","scroll"];class s extends e{toggle(){const e=this.isWrapperHidden?this.showEventNameValue:this.hideEventNameValue;this.isWrapperHidden&&this.showWrapper(),this.wrapperTarget.dispatchEvent(new CustomEvent(e))}get isWrapperHidden(){return this.wrapperTarget.classList.contains(this.hiddenClass)}showWrapper(){this.wrapperTarget.classList.remove(this.hiddenClass)}hideWrapper(){this.wrapperTarget.classList.add(this.hiddenClass)}}s.targets=["wrapper"],s.classes=["hidden"],s.values={showEventName:String,hideEventName:String};const n=[[i,"clipboard_controller.js"],[r,"form_controller.js"],[s,"mobile_menu_controller.js"]].map(function(e){const i=e[0];return{identifier:t(e[1]),controllerConstructor:i}});document.addEventListener("turbo:load",()=>{navigator.userAgent.toLocaleLowerCase().includes("electron")&&document.body.classList.add("electron")});export{n as controllerDefinitions};
2
+ //# sourceMappingURL=bullet-train.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bullet-train.js","sources":["../../../node_modules/@hotwired/stimulus-webpack-helpers/dist/stimulus-webpack-helpers.js","../../javascript/controllers/clipboard_controller.js","../../javascript/controllers/form_controller.js","../../javascript/controllers/mobile_menu_controller.js","../../javascript/controllers/index.js","../../javascript/electron/index.js"],"sourcesContent":["/*\nStimulus Webpack Helpers 1.0.0\nCopyright © 2021 Basecamp, LLC\n */\nfunction definitionsFromContext(context) {\n return context.keys()\n .map((key) => definitionForModuleWithContextAndKey(context, key))\n .filter((value) => value);\n}\nfunction definitionForModuleWithContextAndKey(context, key) {\n const identifier = identifierForContextKey(key);\n if (identifier) {\n return definitionForModuleAndIdentifier(context(key), identifier);\n }\n}\nfunction definitionForModuleAndIdentifier(module, identifier) {\n const controllerConstructor = module.default;\n if (typeof controllerConstructor == \"function\") {\n return { identifier, controllerConstructor };\n }\n}\nfunction identifierForContextKey(key) {\n const logicalName = (key.match(/^(?:\\.\\/)?(.+)(?:[_-]controller\\..+?)$/) || [])[1];\n if (logicalName) {\n return logicalName.replace(/_/g, \"-\").replace(/\\//g, \"--\");\n }\n}\n\nexport { definitionForModuleAndIdentifier, definitionForModuleWithContextAndKey, definitionsFromContext, identifierForContextKey };\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = ['source', 'input', 'button']\n\n copy() {\n this.inputTarget.value = this.sourceTarget.innerText\n this.inputTarget.select()\n document.execCommand('copy')\n this.buttonTarget.innerHTML = '<i id=\"copied\" class=\"fas fa-check w-4 h-4 block text-green-600\"></i>'\n setTimeout(function () {\n document.getElementById('copied').innerHTML = '<i class=\"far fa-copy w-4 h-4 block text-gray-600\"></i>'\n }, 1500)\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\n// TODO Some of this feels really specific to the conversation messages form. Should we rename this controller?\nexport default class extends Controller {\n static targets = ['trixField', 'scroll']\n\n resetOnSuccess(e){\n if(e.detail.success) {\n e.target.reset();\n }\n }\n\n stripTrix(){\n this.trixFieldTargets.forEach(element => {\n this.removeTrailingNewlines(element.editor)\n this.removeTrailingWhitespace(element.editor)\n // When doing this as part of the form submission, Trix does not update the input element's value attribute fast enough.\n // In order to submit the stripped value, we manually update it here to fix the race condition\n element.parentElement.querySelector(\"input\").value = element.innerHTML\n })\n }\n\n submitOnReturn(e) {\n if((e.metaKey || e.ctrlKey) && e.keyCode == 13) {\n e.preventDefault();\n let form = e.target.closest(\"form\")\n this.submitForm(form)\n }\n }\n\n removeTrailingNewlines = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/<br><\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -10) + \"</div>\"\n this.removeTrailingNewlines(trixEditor)\n }\n }\n\n removeTrailingWhitespace = (trixEditor) => {\n if (trixEditor.element.innerHTML.match(/&nbsp;<\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -12) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n } else if (trixEditor.element.innerHTML.match(/&nbsp; <\\/div>$/)) {\n trixEditor.element.innerHTML = trixEditor.element.innerHTML.slice(0, -13) + \"</div>\"\n this.removeTrailingWhitespace(trixEditor)\n }\n }\n\n submitForm(form) {\n // Right now, Safari and IE don't support the requestSubmit method which is required for Turbo\n // Doing form.submit() doesn't actually fire the submit event which Turbo needs\n if (form.requestSubmit) {\n form.requestSubmit()\n } else {\n form.querySelector(\"[type=submit]\").click()\n }\n }\n}\n","import { Controller } from \"@hotwired/stimulus\"\n\nexport default class extends Controller {\n static targets = [ \"wrapper\"]\n static classes = [ \"hidden\" ] // necessary because stimulus-reveal will mess with the [hidden] attribute on the wrapper\n static values = {\n showEventName: String,\n hideEventName: String,\n }\n\n toggle() {\n const eventName = this.isWrapperHidden? this.showEventNameValue: this.hideEventNameValue\n if (this.isWrapperHidden) {\n this.showWrapper()\n }\n \n this.wrapperTarget.dispatchEvent(new CustomEvent(eventName))\n }\n \n get isWrapperHidden() {\n return this.wrapperTarget.classList.contains(this.hiddenClass)\n }\n \n showWrapper() {\n this.wrapperTarget.classList.remove(this.hiddenClass)\n }\n \n hideWrapper() {\n this.wrapperTarget.classList.add(this.hiddenClass)\n }\n}","import { identifierForContextKey } from \"@hotwired/stimulus-webpack-helpers\"\n\nimport ClipboardController from './clipboard_controller'\nimport FormController from './form_controller'\nimport MobileMenuController from './mobile_menu_controller'\n\nexport const controllerDefinitions = [\n [ClipboardController, 'clipboard_controller.js'],\n [FormController, 'form_controller.js'],\n [MobileMenuController, 'mobile_menu_controller.js'],\n].map(function(d) {\n const key = d[1]\n const controller = d[0]\n return {\n identifier: identifierForContextKey(key),\n controllerConstructor: controller\n }\n})\n","document.addEventListener(\"turbo:load\", () => {\n if (navigator.userAgent.toLocaleLowerCase().includes('electron')) {\n document.body.classList.add('electron')\n }\n})"],"names":["identifierForContextKey","key","logicalName","match","replace","Controller","copy","this","inputTarget","value","sourceTarget","innerText","select","document","execCommand","buttonTarget","innerHTML","setTimeout","getElementById","targets","removeTrailingNewlines","trixEditor","element","slice","removeTrailingWhitespace","resetOnSuccess","e","detail","success","target","reset","stripTrix","trixFieldTargets","forEach","editor","parentElement","querySelector","submitOnReturn","metaKey","ctrlKey","keyCode","preventDefault","form","closest","submitForm","requestSubmit","click","toggle","eventName","isWrapperHidden","showEventNameValue","hideEventNameValue","showWrapper","wrapperTarget","dispatchEvent","CustomEvent","classList","contains","hiddenClass","remove","hideWrapper","add","classes","values","showEventName","String","hideEventName","controllerDefinitions","ClipboardController","FormController","MobileMenuController","map","d","controller","identifier","controllerConstructor","addEventListener","navigator","userAgent","toLocaleLowerCase","includes","body"],"mappings":"gDAqBA,SAASA,EAAwBC,GAC7B,MAAMC,GAAeD,EAAIE,MAAM,2CAA6C,IAAI,GAChF,GAAID,EACA,OAAOA,EAAYE,QAAQ,KAAM,KAAKA,QAAQ,MAAO,sBCtBhCC,EAG3BC,OACEC,KAAKC,YAAYC,MAAQF,KAAKG,aAAaC,UAC3CJ,KAAKC,YAAYI,SACjBC,SAASC,YAAY,QACrBP,KAAKQ,aAAaC,UAAY,wEAC9BC,WAAW,WACTJ,SAASK,eAAe,UAAUF,UAAY,2DAC7C,SATEG,QAAU,CAAC,SAAU,QAAS,0BCAVd,yCA2B3Be,uBAA0BC,IACpBA,EAAWC,QAAQN,UAAUb,MAAM,kBACrCkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKa,uBAAuBC,UAIhCG,yBAA4BH,IACtBA,EAAWC,QAAQN,UAAUb,MAAM,mBACrCkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKiB,yBAAyBH,IACrBA,EAAWC,QAAQN,UAAUb,MAAM,qBAC5CkB,EAAWC,QAAQN,UAAYK,EAAWC,QAAQN,UAAUO,MAAM,GAAI,IAAM,SAC5EhB,KAAKiB,yBAAyBH,KArClCI,eAAeC,GACVA,EAAEC,OAAOC,SACVF,EAAEG,OAAOC,QAIbC,YACExB,KAAKyB,iBAAiBC,QAAQX,IAC5Bf,KAAKa,uBAAuBE,EAAQY,QACpC3B,KAAKiB,yBAAyBF,EAAQY,QAGtCZ,EAAQa,cAAcC,cAAc,SAAS3B,MAAQa,EAAQN,YAIjEqB,eAAeX,GACb,IAAIA,EAAEY,SAAWZ,EAAEa,UAA0B,IAAbb,EAAEc,QAAe,CAC/Cd,EAAEe,iBACF,IAAIC,EAAOhB,EAAEG,OAAOc,QAAQ,QAC5BpC,KAAKqC,WAAWF,IAqBpBE,WAAWF,GAGLA,EAAKG,cACPH,EAAKG,gBAELH,EAAKN,cAAc,iBAAiBU,WAjDjC3B,QAAU,CAAC,YAAa,0BCFJd,EAQ3B0C,SACE,MAAMC,EAAYzC,KAAK0C,gBAAiB1C,KAAK2C,mBAAoB3C,KAAK4C,mBAClE5C,KAAK0C,iBACP1C,KAAK6C,cAGP7C,KAAK8C,cAAcC,cAAc,IAAIC,YAAYP,IAG/CC,sBACF,YAAYI,cAAcG,UAAUC,SAASlD,KAAKmD,aAGpDN,cACE7C,KAAK8C,cAAcG,UAAUG,OAAOpD,KAAKmD,aAG3CE,cACErD,KAAK8C,cAAcG,UAAUK,IAAItD,KAAKmD,gBAzBjCvC,QAAU,CAAE,aACZ2C,QAAU,CAAE,YACZC,OAAS,CACdC,cAAeC,OACfC,cAAeD,QCDNE,MAAAA,EAAwB,CACnC,CAACC,EAAqB,2BACtB,CAACC,EAAgB,sBACjB,CAACC,EAAsB,8BACvBC,IAAI,SAASC,GACb,MACMC,EAAaD,EAAE,GACrB,MAAO,CACLE,WAAY1E,EAHFwE,EAAE,IAIZG,sBAAuBF,KCf3B5D,SAAS+D,iBAAiB,aAAc,KAClCC,UAAUC,UAAUC,oBAAoBC,SAAS,aACnDnE,SAASoE,KAAKzB,UAAUK,IAAI"}
@@ -1,81 +1,122 @@
1
- # Enabling Stripe Subscriptions
1
+ # Bullet Train Billing for Stripe
2
2
 
3
- Bullet Train provides a base billing package and a Stripe-specific package with support for Stripe Checkout, Stripe Billing's customer portal, and incoming Stripe webhooks.
3
+ When you're ready to start billing customers for the product you've created with Bullet Train, you can take advantage of our streamlined, commercial billing package that includes a traditional SaaS pricing page powered by Yaml configuration for products and prices.
4
4
 
5
- ## 1. Add the `bullet_train-billing-stripe` Ruby gem.
5
+ We also provide a Stripe-specific adapter package with support for auto-configuring those products and prices in your Stripe account. It also takes advantage of completely modern Stripe workflows, like allowing customers to purchase your product with Stripe Checkout and later manage their subscription using Stripe Billing's customer portal. It also automatically handles incoming Stripe webhooks as well, to keep subscription state in your application up-to-date with activity that has happened on Stripe's platform.
6
6
 
7
- > TODO Add instructions about subscribing to Bullet Train Pro.
7
+ ## 1. Purchase Bullet Train Billing for Stripe
8
8
 
9
- ## 2. Migrate the Database
9
+ First, [purchase Bullet Train Billing for Stripe](https://buy.stripe.com/28o8zg4dBbrd59u7sM). Once you've completed this process, you'll be issued a private token for the Bullet Train Pro package server. (This process is currently completed manually, so please be patient.)
10
+
11
+ ## 2. Install the Package
12
+
13
+ ### 2.1. Add the Private Ruby Gems
14
+
15
+ You'll need to specify both Ruby gems in your `Gemfile`, since we have to specify a private source for both:
16
+
17
+ ```
18
+ source "https://YOUR_TOKEN_HERE@gem.fury.io/bullettrain" do
19
+ gem "bullet_train-billing"
20
+ gem "bullet_train-billing-stripe"
21
+ end
22
+ ```
23
+
24
+ ### 2.2. Bundle Install
25
+
26
+ ```
27
+ bundle install
28
+ ```
29
+
30
+ ### 2.3. Copy Database Migrations
31
+
32
+ Use the following two commands on your shell to copy the required migrations into your local project:
33
+
34
+ ```
35
+ cp `bundle show --paths | grep bullet_train-billing | sort | head -n 1`/db/migrate/* db/migrate
36
+ cp `bundle show --paths | grep bullet_train-billing-stripe | sort | head -n 1`/db/migrate/* db/migrate
37
+ ```
38
+
39
+ Note this is different than how many Rails engines ask you to install migrations. This is intentional, as we want to maintain the original timestamps associated with these migrations.
40
+
41
+ ### 2.4. Run Migrations
10
42
 
11
43
  ```
12
44
  rake db:migrate
13
45
  ```
14
46
 
15
- ## 3. Create API Keys with Stripe
47
+ ## 3. Configure Your Products
48
+
49
+ Bullet Train defines subscription plans and pricing options in `config/models/billing/products.yml` and defines the translatable elements of these plans in `config/locales/en/billing/products.en.yml`. We recommend just getting started with these plans to ensure your setup is working before customizing the attributes of these plans.
50
+
51
+ ## 4. Configure Stripe
52
+
53
+ ### 4.1. Create API Keys with Stripe
16
54
 
17
55
  - Create a Stripe account if you don't already have one.
18
- - Visit https://dashboard.stripe.com/apikeys.
19
- - For your development environment, make sure you toggle the "test mode" flag to "on" in the top-right corner.
56
+ - Visit https://dashboard.stripe.com/test/apikeys.
20
57
  - Create a new secret key.
21
58
 
22
- ## 4. Configure Stripe API Keys Locally
59
+ **Note:** By default we're linking to the "test mode" page for API keys so you can get up and running in development. When you're ready to deploy to production, you'll have to repeat this step and toggle the "test mode" option off to provision real API keys for live payments.
23
60
 
24
- Edit `config/application.yml` and add your Stripe publishable key and new secret key to the file, and also tell the system to use Stripe subscriptions by default:
61
+ ### 4.2. Configure Stripe API Keys Locally
62
+
63
+ Edit `config/application.yml` and add your new Stripe secret key to the file:
25
64
 
26
65
  ```
27
- BILLING_DEFAULT_SUBSCRIPTION: "Billing::Stripe::Subscription"
28
- STRIPE_PUBLISHABLE_KEY: pk_0CJwz5wHlKBXxDA4VO1uEoipxQob0
29
66
  STRIPE_SECRET_KEY: sk_0CJw2Iu5wwIKXUDdqphrt2zFZyOCH
30
67
  ```
31
68
 
32
- ## 5. Populate Stripe with Locally Configured Products
33
-
34
- Bullet Train defines subscription plans and other purchasable add-ons in `config/models/billing/products.yml` and comes preconfigured with some example plans. We recommend just getting started with these plans to ensure your setup is working before customizing the attributes of these plans.
69
+ ### 4.3. Populate Stripe with Locally Configured Products
35
70
 
36
- Before you can use Stripe Checkout or Stripe Billing's customer portal, these products will have to be defined on Stripe as well. You can have all locally defined products automatically created on Stripe by running the following:
71
+ Before you can use Stripe Checkout or Stripe Billing's customer portal, your locally configured products will have to be created on Stripe as well. To accomplish this, you can have all locally defined products automatically created on Stripe via API by running the following:
37
72
 
38
73
  ```
39
74
  rake billing:stripe:populate_products_in_stripe
40
75
  ```
41
76
 
42
- ## 6. Import Additional Environment Variables
77
+ ### 4.4. Add Additional Environment Variables
43
78
 
44
79
  The script in the previous step will output some additional environment variables you need to copy into `config/application.yml`.
45
80
 
46
- ## 7. Restart Rails
47
81
 
48
- We've modified a bunch of environment variables, so you'll have to have to restart your Rails server before you see the results in your browser.
82
+ ## 5. Wire Up Webhooks
49
83
 
50
- ```
51
- rails restart
52
- ```
84
+ Basic subscription creation will work without receiving and processing Stripe's webhooks. However, advanced payment workflows like SCA payments and customer portal cancelations and plan changes require receiving webhooks and processing them.
53
85
 
54
- ## 8. Test Creating a Subscription
86
+ ### 5.1. Ensure HTTP Tunneling is Enabled
55
87
 
56
- Bullet Train comes preconfigured with a "freemium" plan, so new and existing accounts will continue to work as normal. A new "billing" menu item will appear and you can test subscription creation by clicking "upgrade" and selecting one of the two plans presented.
88
+ Although Stripe provides free tooling for receiving webhooks in your local environment, the officially supported mechanism for doing so in Bullet Train is using [HTTP Tunneling with ngrok](/docs/tunneling.md). This is because we provide support for many types of webhooks across different platforms and packages, so we already need to have ngrok in play.
57
89
 
58
- You should be in "test mode" on Stripe, so when prompted for a credit card number, you can enter `4242 4242 4242 4242`.
90
+ Ensure you've completed the steps from [HTTP Tunneling with ngrok](/docs/tunneling.md), including updating `BASE_URL` in `config/application.yml` and restarting your Rails server.
59
91
 
60
- ## 9. Configuring Webhooks
92
+ ### 5.2. Enable Stripe Webhooks
61
93
 
62
- Basic subscription creation will work without receiving and processing Stripe's webhooks. However, advanced payment workflows like SCA payments and customer portal cancelations and plan changes require receiving webhooks and processing them.
63
-
64
- - Stripe can't deliver webhooks to `http://localhost:3000`, so you'll need to [get an HTTP tunnel up and running](/docs/tunneling.md). For this example, we'll assume you're using ngrok.
65
94
  - Visit https://dashboard.stripe.com/test/webhooks/create.
66
- - Configure the "endpoint URL" to be `https://your-tunnel.ngrok.io/webhooks/incoming/stripe_webhooks`, replacing `your-tunnel` with whatever the subdomain of your tunnel is.
67
- - When configuring which events to receive, just "select all events" for simplicity. This ensures that any webhooks Bullet Train might add support for in the future will be properly handled when you upgrade.
68
- - Add the endpoint.
69
- - On the page for the webhook endpoint you've just configured with Stripe, click on "reveal" under the heading "signing secret". This is a secret token that is required to authenticate that webhooks your application is receiving are actually coming from Stripe. Copy this into your `config/application.yml` like so:
95
+ - Use the default "add an endpoint" form.
96
+ - Set "endpoint URL" to `https://YOUR-SUBDOMAIN.ngrok.io/webhooks/incoming/stripe_webhooks`.
97
+ - Under "select events to listen to" choose "select all events" and click "add events".
98
+ - Finalize the creation of the endpoint by clicking "add endpoint".
99
+
100
+ ### 5.3. Configure Stripe Webhooks Signing Secret
101
+
102
+ After creating the webhook endpoint, click "reveal" under the heading "signing secret". Copy the `whsec_...` value into your `config/application.yml` like so:
103
+
104
+ ```
105
+ STRIPE_WEBHOOKS_ENDPOINT_SECRET: whsec_vchvkw3hrLK7SmUiEenExipUcsCgahf9
106
+ ```
70
107
 
71
- ```
72
- STRIPE_WEBHOOKS_ENDPOINT_SECRET: whsec_VsM3c2zeZyqAddkaPaXzf1wJsYp2fRKR
73
- ```
108
+ ### 5.4. Test Sample Webhook Delivery
74
109
 
75
110
  - Restart your Rails server with `rails restart`.
76
111
  - Trigger a test webhook just to ensure it's resulting in an HTTP status code of 201.
77
112
 
78
- ## 10. Configure Stripe Billing's Customer Portal
113
+ ## 6. Test Creating a Subscription
114
+
115
+ Bullet Train comes preconfigured with a "freemium" plan, so new and existing accounts will continue to work as normal. A new "billing" menu item will appear and you can test subscription creation by clicking "upgrade" and selecting one of the two plans presented.
116
+
117
+ You should be in "test mode" on Stripe, so when prompted for a credit card number, you can enter `4242 4242 4242 4242`.
118
+
119
+ ## 7. Configure Stripe Billing's Customer Portal
79
120
 
80
121
  - Visit https://dashboard.stripe.com/test/settings/billing/portal.
81
122
  - Complete all required fields.
@@ -83,8 +124,23 @@ Basic subscription creation will work without receiving and processing Stripe's
83
124
 
84
125
  This "products" list is what Stripe will display to users as upgrade and downgrade options in the customer portal. You shouldn't list any products here that aren't properly configured in your Rails app, otherwise the resulting webhook will fail to process. If you want to stop offering a plan, you should remove it from this list as well.
85
126
 
86
- ## 11. Test Webhooks by Managing a Subscription
127
+ ## 8. Finalize Webhooks Testing by Managing a Subscription
87
128
 
88
129
  In the same account where you created your first test subscription, go into the "billing" menu and click "manage" on that subscription. This will take you to the Stripe Billing customer portal.
89
130
 
90
131
  Once you're in the customer portal, you should test upgrading, downgrading, and canceling your subscription and clicking "⬅ Return to {Your Application Name}" in between each step to ensure that each change you're making is properly reflected in your Bullet Train application. This will let you know that webhooks are being properly delivered and processed and all the products in both systems are properly mapped in both directions.
132
+
133
+ ## 9. Rinse and Repeat Configuration Steps for Production
134
+
135
+ As mentioned earlier, all of the links we provided for configuration steps on Stripe were linked to the "test mode" on your Stripe account. When you're ready to launch payments in production, you will need to:
136
+
137
+ - Complete all configuration steps again in the live version of your Stripe account. You can do this by following all the links in the steps above and toggling the "test mode" switch to visit the live mode version of each page.
138
+ - After creating a live API key, configure `STRIPE_SECRET_KEY` in your production environment.
139
+ - Run `STRIPE_SECRET_KEY=... rake billing:stripe:populate_products_in_stripe` (where `...` is your live secret key) in order to create live versions of your products and prices.
140
+ - Copy the environment variables output by that rake task into your production environment.
141
+ - Configure a live version of your webhooks endpoint for the production environment by following the same steps as before, but replacing the ngrok host with your production host in the endpoint URL.
142
+ - After creating the live webhooks endpoint, configure the corresponding signing secret as the `STRIPE_WEBHOOKS_ENDPOINT_SECRET` enviornment variable in your production environment.
143
+
144
+ ## 10. You should be done!
145
+
146
+ [Let us know on Discord](http://discord.gg/bullettrain) if any part of this guide was not clear or could be improved!
data/docs/tunneling.md CHANGED
@@ -4,14 +4,14 @@ Before your application can take advantage of features that depend on incoming w
4
4
 
5
5
  ## Use a Paid Plan
6
6
 
7
- You should specifically sign up for a paid account. Although ngrok offers a free plan, their $5/month paid plan will allow you to reserve a custom subdomain for reuse each time you spin up your tunnel. This is a critical productivity improvement, because in practice you'll end up configuring your tunnel URL in a bunch of different places like `config/application.yml` but also in external systems like when you [configure payment providers to deliver webhooks to you](docs/billing/stripe.md).
7
+ You should specifically sign up for a paid account. Although ngrok offers a free plan, their $25/month paid plan will allow you to reserve a custom subdomain for reuse each time you spin up your tunnel. This is a critical productivity improvement, because in practice you'll end up configuring your tunnel URL in a bunch of different places like `config/application.yml` but also in external systems like when you [configure payment providers to deliver webhooks to you](docs/billing/stripe.md).
8
8
 
9
9
  ## Usage
10
10
 
11
- Once you have ngrok installed, you can start your tunnel like so, replacing `your-subdomain` with whatever subdomain you reserved in your ngrok account:
11
+ Once you have ngrok installed, you can start your tunnel like so, replacing `YOUR-SUBDOMAIN` with whatever subdomain you reserved in your ngrok account:
12
12
 
13
13
  ```
14
- ngrok http 3000 -subdomain=your-subdomain
14
+ ngrok http 3000 -subdomain=YOUR-SUBDOMAIN
15
15
  ```
16
16
 
17
17
  ## Updating Your Configuration
@@ -19,7 +19,7 @@ ngrok http 3000 -subdomain=your-subdomain
19
19
  Before your Rails application will accept connections on your tunnel hostname, you need to update `config/application.yml` with:
20
20
 
21
21
  ```
22
- BASE_URL: https://your-subdomain.ngrok.io
22
+ BASE_URL: https://YOUR-SUBDOMAIN.ngrok.io
23
23
  ```
24
24
 
25
25
  You'll also need to restart your Rails server:
@@ -1,3 +1,3 @@
1
1
  module BulletTrain
2
- VERSION = "1.0.83"
2
+ VERSION = "1.0.85"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.83
4
+ version: 1.0.85
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-28 00:00:00.000000000 Z
11
+ date: 2022-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -428,6 +428,8 @@ files:
428
428
  - README.md
429
429
  - Rakefile
430
430
  - app/assets/config/bullet_train_manifest.js
431
+ - app/assets/javascripts/bullet-train.js
432
+ - app/assets/javascripts/bullet-train.js.map
431
433
  - app/controllers/account/invitations_controller.rb
432
434
  - app/controllers/account/memberships_controller.rb
433
435
  - app/controllers/account/onboarding/user_details_controller.rb
@@ -666,7 +668,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
666
668
  - !ruby/object:Gem::Version
667
669
  version: '0'
668
670
  requirements: []
669
- rubygems_version: 3.2.22
671
+ rubygems_version: 3.3.7
670
672
  signing_key:
671
673
  specification_version: 4
672
674
  summary: Bullet Train