149 Commits
Author SHA1 Message Date
Jarek Radosz cf83c0693c DEV: Deprecate the plugin (#281) 2025-07-14 15:12:17 +01:00
dependabot[bot] c7d7e14217 Bump rack from 3.1.12 to 3.1.14 (#275)
Bumps [rack](https://github.com/rack/rack) from 3.1.12 to 3.1.14.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v3.1.12...v3.1.14)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 3.1.14
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-07 12:50:34 -06:00
Mark Doerr 9e5f61832d FEATURE: Add support for SEK currency (#276)
Ref: https://meta.discourse.org/t/adding-new-currency-options-for-the-subscriptions-plugin/316003/12
2025-07-07 08:42:18 -06:00
dependabot[bot] 46f9fea9e2 Bump rack from 3.1.12 to 3.1.16 (#278)
Bumps [rack](https://github.com/rack/rack) from 3.1.12 to 3.1.16.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v3.1.12...v3.1.16)

---
updated-dependencies:
- dependency-name: rack
  dependency-version: 3.1.16
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-21 17:57:28 +02:00
Jarek Radosz fe53f2cbc0 DEV: Clean up helpers (#279)
* remove an unused helper
* move a single-use helper to its call-site as a getter
* convert `format-currency` to a pure function helper
2025-06-20 21:36:54 +02:00
Jarek Radosz 7107966af6 DEV: Update linting config and run gjs-codemod (#277) 2025-06-05 11:40:13 +01:00
Derek Rushforth 33d033c23b FIX: Campaign banner size when sidebar is active (#274) 2025-04-14 10:47:35 -07:00
Derek Rushforth 4771d81bfa UX: Campaign banner layout improvements (#273) 2025-04-10 13:12:38 -07:00
Blake Erickson 100b276c9d DEV: Add pagination to plans request (#272)
When pulling up a users subscriptions a 500 might occur if we don't find
their plan. If your account happens to have over 100 plans this can be
the cause of the 500 error. This change adds pagination when fetching
plans form stripe, so that we fetch them all.

https://meta.discourse.org/t/problem-with-500-error-with-subscriptions/360808?u=blake
2025-04-10 07:35:31 -06:00
Loïc Guitaut 3dd59a60da DEV: Add system spec to check core features are working fine (#271) 2025-04-09 14:56:06 +02:00
Derek Rushforth b0af86f12b Campaign banner styles (#270) 2025-03-28 14:21:54 -07:00
Jarek Radosz 854b3f223f DEV: Update linting (#269) 2025-03-17 11:41:43 +00:00
Penar Musaraj 9e38a69dbb fix spec for full page logins (#268) 2025-03-13 13:20:21 -04:00
Tomas Vavrda 208ecb66a3 FEATURE: Add support for CZK currency (#265) 2025-02-18 14:48:04 -07:00
Tomas Vavrda 832c52d5d7 FIX: FontAwesome 6 compatibility icon upgrade (#264)
Mitigates `Deprecation notice: The icon name "donate" has been updated to "circle-dollar-to-slot". Please use the new name in your code. Old names will be removed in Q2 2025. [deprecation id: discourse.fontawesome-6-upgrade] [info: https://meta.discourse.org/t/325349]` message.
2025-02-15 20:51:08 +01:00
dependabot[bot] cc43d32af7 Bump rack from 3.0.11 to 3.0.12 (#263)
Bumps [rack](https://github.com/rack/rack) from 3.0.11 to 3.0.12.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/commits/v3.0.12)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-13 23:27:47 +00:00
David Taylor da48acad9a DEV: Bump dependencies and fix linting (#262) 2025-02-06 17:57:53 +01:00
David Taylor 721c4f28f1 DEV: Pin version for Discourse <3.5.0.beta1-dev (#261) 2025-02-05 20:17:39 +01:00
Discourse Translator Bot b4f7be4274 Update translations (#260) 2025-01-21 15:56:02 +01:00
David Taylor 001a25c63c DEV: Colocate component templates (#259) 2025-01-09 17:43:06 +01:00
Discourse Translator Bot a780c77f6c Update translations (#257) 2024-12-18 15:19:57 +01:00
Blake Erickson 02ba5e1ff2 FEATURE: Add support for PLN currency (#258)
Stripe supports the Polish złoty so we can add support for it.

https://meta.discourse.org/t/342040
2024-12-17 14:58:05 -07:00
Discourse Translator Bot 2c46e3cffb Update translations (#256) 2024-12-10 20:17:27 +01:00
Kelv 73db0b208a DEV: Update more deprecated Font Awesome icon names (#253) 2024-12-06 20:40:32 +01:00
Discourse Translator Bot cdd797c68f Update translations (#255) 2024-12-06 18:05:25 +01:00
Blake Erickson 1811b5955d FEATURE: Add support for CHF currency (#254)
Stripe supports the Swiss Franc so we can add support for it.

https://meta.discourse.org/t/316003
2024-12-05 11:01:31 -07:00
Blake Erickson d82b34080b DEV: Fix typo in site setting description (#252)
change permantly to permanently
2024-12-04 11:02:16 -07:00
Kelv 9263d97d64 DEV: Update deprecated Font Awesome icon names (#251) 2024-12-04 02:36:19 +01:00
Jarek Radosz 692f410ab5 DEV: Update components tests (#250) 2024-11-29 19:51:21 +01:00
David Taylor 3149828c22 DEV: Convert to native class syntax (#249) 2024-11-29 16:42:50 +01:00
David Taylor c7a35e1c89 DEV: Update linting (#248) 2024-11-28 17:14:17 +01:00
David Taylor 12cac3827c DEV: Update linting (#247) 2024-11-20 18:34:34 +01:00
dependabot[bot] 9a0369f272 Bump cross-spawn from 7.0.3 to 7.0.6 (#246)
Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6.
- [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6)

---
updated-dependencies:
- dependency-name: cross-spawn
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-19 10:57:13 +00:00
David Taylor 7b15f57af3 DEV: Update eslint config (#245) 2024-11-19 11:31:14 +01:00
dependabot[bot] ce2f0899c6 Bump rexml from 3.3.6 to 3.3.9 (#244)
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.6 to 3.3.9.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-28 20:40:26 +01:00
Gabriel Grubba dff47e2a3a DEV: Update plugin annotations (#243) 2024-10-25 09:48:56 +01:00
Blake Erickson f40d6f9cad DEV: Add some more verbose logging for debugging groups (#242)
Sometimes users aren't added to groups so adding some more logging
statements to aid in debugging.
2024-10-18 14:29:42 -06:00
Kelv 185071aa03 DEV: Switch to use pnpm (#241) 2024-10-14 14:39:36 +02:00
Bianca Nenciu f47400a9b1 DEV: Fix mismatched column types (#240)
The primary key is usually a bigint column, but the foreign key columns
are usually of integer type. This can lead to issues when joining these
columns due to mismatched types and different value ranges.
2024-10-10 16:29:35 +03:00
Blake EricksonandJarek Radosz eecb17698f FIX: Redirect to the pricing table page when enabled (#239)
If the pricing table is enabled the `/s` route should redirect to the
pricing table route and vice versa.

Co-authored-by: Jarek Radosz <[email protected]>
2024-10-02 17:53:20 -06:00
Jarek Radosz 937d099692 DEV: Prefix all test names (#238) 2024-09-18 01:49:28 +02:00
dependabot[bot] ef820ca2e0 Bump micromatch from 4.0.5 to 4.0.8 (#237)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-01 12:32:55 +02:00
dependabot[bot] f1b9c967c3 Bump rexml from 3.3.3 to 3.3.6 (#229)
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.3 to 3.3.6.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.3...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-29 15:02:37 -06:00
Discourse Translator Bot dd846ae87d Update translations (#235) 2024-08-29 15:01:43 -06:00
Blake Erickson 9c5ec86de7 DEV: Guest payments follow up (#236)
Just some minor follow up changes to PR #234
2024-08-29 14:49:03 -06:00
Blake Erickson fa5e95860b DEV: Fetch related guest payments (#234) 2024-08-28 21:51:34 -06:00
Blake Erickson 700c5e7727 FIX: Payments not showing up for users (#233)
Pricing table payments for one off purchases need to be attached to the
customer or they don't show up on the user's billing payments page.
2024-08-28 15:03:22 -06:00
Blake Erickson a2c70deeff FIX: Create customer for one off pricing table purchases (#232)
When using the Stripe Pricing Table for one-off purchases
(non-subscription) stripe doesn't automatically create a customer
record. This was causing the pricing table webhook
endpoint to end prematurely and not add users to their appropriate
group after purchase. Now if we detect that there is no customer record
we will make an api call to Stripe to create one.
2024-08-28 11:27:55 -06:00
Blake Erickson 71643a023d DEV: Use warn instead of info for Rails logger (#231)
Follow up to: e92dd39928
2024-08-28 09:17:08 -06:00
Blake Erickson e92dd39928 DEV: Add verbose logging site setting (#230)
To aid in debugging I'm adding a verbose logging site setting. I'm
adding this because I'm currently getting some errors in production that
I'm unable to replicate locally.

See: https://meta.discourse.org/t/323450/4
2024-08-27 18:40:12 -06:00
Blake Erickson f0b4984cee FIX: Pricing table for one-off purchases (#228)
When using the Stripe Pricing table for one-off purchases
(non-subscriptions) the webhook was encountering an error because it was
expecting subscription information which does not exist for one-off
purchases. This fix addresses that issue ensuring that no errors occur,
that users are added to the appropriate group, and that uses can see
their purchase on the billing page.
2024-08-20 13:32:32 -06:00
Discourse Translator Bot 52cae7b1a9 Update translations (#227) 2024-08-13 16:32:20 +02:00
dependabot[bot] 0f52101f2e Bump rexml from 3.2.8 to 3.3.3 (#226)
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.8 to 3.3.3.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.8...v3.3.3)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-05 13:20:22 +02:00
Natalie Tay 2824f955c2 DEV: Pin plugin for Discourse < 3.4.0.beta1-dev (#225) 2024-08-02 17:12:58 +08:00
Discourse Translator Bot bb96d77056 Update translations (#224) 2024-07-31 00:15:07 +02:00
Blake Erickson 41c443ab23 FIX: Decimal point truncation (#223)
Amounts like $45.80 were being displayed as $45.8. Which doesn't look
correct for currency.

Bug Report: https://meta.discourse.org/t/316007
2024-07-19 07:18:28 -06:00
Discourse Translator Bot 403c966514 Update translations (#221) 2024-07-16 22:14:17 +02:00
Blake Erickson 572aa0f4a9 FIX: Exception with campaign refresh data job (#222)
Depending on the invoices in stripe there could be some nil values for
things so we need to account for those in this job or an exception will
be thrown causing the job to fail.
2024-07-16 11:54:56 -06:00
Blake Erickson c02193943b FIX: Admins unable to cancel a subscription for a user (#220) 2024-07-12 14:33:04 -06:00
Discourse Translator Bot 0fca89a1f7 Update translations (#219) 2024-07-09 23:12:43 +02:00
Blake Erickson 3cc113973f FEATURE: Site Setting to enable automatic tax (#218)
FEATURE: Site Setting to enable automatic tax

There is now a new site setting called
`discourse_subscriptions_enable_automatic_tax` that can be enabled if
you would like to automatically compute tax rates on all new
subscriptions.
2024-07-08 13:22:02 -06:00
dependabot[bot] a3b4521a76 Bump braces from 3.0.2 to 3.0.3 (#217)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-16 22:31:59 +02:00
Discourse Translator Bot e9817406f1 Update translations (#216) 2024-05-31 12:30:13 +02:00
Loïc Guitaut 381e866009 DEV: Update rubocop-discourse to version 3.8.0 (#215) 2024-05-28 11:52:36 +02:00
Ted Johansson 804f5303f4 FIX: Add missing transaction error translation (#214) 2024-05-24 18:27:00 +08:00
dependabot[bot] cac4bdd4ff Bump rexml from 3.2.6 to 3.2.8 (#213)
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.6 to 3.2.8.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.6...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-17 00:02:28 +02:00
Blake Erickson 542af4cf16 FIX: Campaign banner should link to pricing table when enabled (#212)
If the new pricing table is enabled the campaign banner should link to
the pricing table route.
2024-05-15 11:44:27 -06:00
Discourse Translator Bot 512f37a1d3 Update translations (#211) 2024-05-14 16:20:09 +02:00
Blake Erickson d63c84eca5 FIX: One-time purchase pending invoice item (#210)
This change ensures we attach the invoice item to the invoice to avoid
any occurrences of an empty invoice being paid with pending invoice
items.
2024-05-07 08:57:37 -06:00
Blake Erickson e132913db4 FIX: Login required for Stripe Checkout (#209)
If an anonymous user tries to subscribe we need to show them a log in
message first. We currently don't have support for anonymous
subscriptions.
2024-05-03 17:01:51 -06:00
Blake Erickson 5eba613f8f FIX: Prevent duplicate stripe subscription list requests (#208)
Users can have multiple customer records in our db, so we need to filter
out non-unique stripe customer ids before fetching subscriptions for each.
2024-05-03 12:38:26 -06:00
Blake Erickson 66e8857c20 DEV: Stop deleting customers on cancel (#207)
Instead of deleting customers on cancel we will now update the
subscription status to canceled. This way we can have some visibility on
which users have canceled.
2024-05-02 13:38:30 -06:00
Discourse Translator Bot aaa4baec8a Update translations (#205) 2024-04-30 21:57:46 +02:00
Blake Erickson b1920caf7d FIX: Auto-Create Support Campaign button (#206)
The Auto-Create Support Campaign button was just showing an infinite "is
loading" spinner so you couldn't even click on it.

This fix allows the button to work again and only show the loading
spinner when it is actually loading.

See: https://meta.discourse.org/t/discourse-subscriptions/140818/653?u=blake
2024-04-30 09:27:04 -06:00
Blake Erickson 7ee0b696ba FIX: Update cc save button missing (#204) 2024-04-30 06:00:55 -06:00
Blake Erickson 6f54494a3b FIX: Allow active status for updated webhook (#203)
Likely there was an API change at some point now that we are using the
latest version of the stripe API. When the
`customer.subscription.updated` webhook is called we should also accept
the status of "active".
2024-04-29 15:50:05 -06:00
Blake Ericksonandspirobel 45754baa00 FEATURE: Add new stripe based pricing table (#202)
This commit adds an optional new stripe based pricing table.
If the user is logged in, the email field will be prepopulated with the users email.

The pricing table can be configured in the stripe dashboard.

Once the discourse_subscriptions_pricing_table setting is filled with the pricing table embed code from the stripe dashboard,
the pricing table will be displayed on /s/subscriptions

For more details see https://stripe.com/docs/payments/checkout/pricing-table
---------

Co-authored-by: spirobel <[email protected]>
2024-04-29 12:47:58 -06:00
Blake Erickson dcde03d7c4 DEV: Bump stripe gem (#200)
Upgrading to the latest stripe gem and api version.
2024-04-16 11:28:07 -06:00
Michelle Bueno Saquetim Vendrame d63f241e0d FEATURE: add ZAR currency support (#199) 2024-04-15 21:26:26 +02:00
Discourse Translator Bot 8f1b332f9e Update translations (#198) 2024-04-02 18:20:05 +02:00
Blake Erickson 9190001a3c FIX: Translation missing for Norway (#196)
Turns out in YAML `NO` is interpreted as a boolean value, so we need to
wrap it in quotes.
2024-03-27 15:50:15 -06:00
Natalie Tay 0d28c9a56d FIX: Ensure deletion of product upon confirmation (#195)
Ensure deletion of product upon confirmation
2024-03-13 17:16:07 +08:00
David Taylor 7361721ec0 DEV: Update for strict-dynamic CSP (#194) 2024-03-06 16:45:55 +00:00
Loïc Guitaut 87659e237b DEV: Fix new Rubocop offenses 2024-03-06 15:23:55 +01:00
Selase Krakani 7b8a41ad0d DEV: Pin plugin for Discourse < 3.3.0.beta1-dev (#192) 2024-02-29 13:59:04 -07:00
Discourse Translator Bot 0dcf17de59 Update translations (#191) 2024-01-30 17:04:52 +01:00
Discourse Translator Bot 2cfded9a77 Update translations (#190) 2024-01-23 18:30:34 +01:00
Jarek Radosz d33cf2921e DEV: Update linting (#189) 2024-01-16 17:51:44 +01:00
David Taylor a3883d2a9a DEV: Resolve registerUnbound deprecation (#188) 2024-01-15 11:52:19 +00:00
Isaac Janzen 331c8630c3 DEV: Upgrade modal to Component API (#186)
- Upgrade modal to Component API
- Co-locate templates
2023-12-08 15:25:05 -07:00
David Taylor 2ea8d7b6eb DEV: Resolve transitionTo deprecation (#185) 2023-12-01 18:30:36 +00:00
Discourse Translator Bot 316303da5d Update translations (#184) 2023-11-21 14:37:08 +01:00
Discourse Translator Bot 2f9353bc33 Update translations (#183) 2023-11-14 14:30:37 +01:00
Martin Brennan 3608944b9c DEV: Change settings root from plugins: to discourse_subscriptions (#182)
This is so the plugins settings are better categorized in the site settings UI.
2023-11-13 10:23:25 +10:00
Martin BrennanandAlexDev 3aec1a9336 DEV: Metadata update for plugin.rb (#181)
* Update plugin.rb metadata

* Update plugin.rb

---------

Co-authored-by: AlexDev <[email protected]>
2023-11-07 17:29:37 -08:00
Discourse Translator Bot 2f87566366 Update translations (#180) 2023-11-07 21:31:30 +01:00
Kris ac81a7a830 fix typo (#179) 2023-10-24 12:39:32 -04:00
dependabot[bot] a257b386f9 Bump @babel/traverse from 7.20.10 to 7.23.2 (#178)
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.20.10 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-18 12:34:58 -04:00
Natalie Tay c30fea21ef FIX: Clicking on login should show login modal (#177)
* FIX: Clicking on login should show login modal

* Add system test that shows the login modal appears
2023-09-28 14:21:16 +08:00
alex-tee b855ce9bb9 add JPY currency (#176) 2023-09-06 12:14:41 +02:00
Penar Musaraj 1a80f9d52b DEV: Pin plugin for Discourse 3.1 stable (#175) 2023-08-22 13:50:37 -05:00
Discourse Translator Bot c4f8220a55 Update translations (#174) 2023-08-15 21:25:59 +02:00
Penar Musaraj 7140517ec8 DEV: Fix SCSS deprecations (#172)
* DEV: Fix SCSS deprecations

Followup to https://github.com/discourse/discourse-subscriptions/pull/171

Also fixes:

```
    ╷
581 │       hsl(random(360), 100, 50);
    │       ^^^^^^^^^^^^^^^^^^^^^^^^^
    ╵
    /Users/pmusaraj/Projects/discourse/discourse-subscriptions.scss 581:7  root stylesheet
Deprecation Warning: $lightness: Passing a number without unit % (50) is deprecated.
```

* More cleanup
2023-07-19 11:15:34 -04:00
dependabot[bot] 9173190181 Bump word-wrap from 1.2.3 to 1.2.4 (#173)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-19 01:57:35 +02:00
Penar Musaraj 77cb62bf1f DEV: Fixes SCSS deprecation in console (#171)
Sample warning was:

```
Using / for division outside of calc() is deprecated and will be removed in Dart Sass 2.0.0.

Recommendation: math.div($sidebar-height, 1.2) or calc($sidebar-height / 1.2)

More info and automated migrator: https://sass-lang.com/d/slash-div
```
2023-07-17 16:42:06 -04:00
dependabot[bot] 2a75da65b8 Bump semver from 6.3.0 to 6.3.1 (#170)
Bumps [semver](https://github.com/npm/node-semver) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/npm/node-semver/releases)
- [Changelog](https://github.com/npm/node-semver/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/npm/node-semver/compare/v6.3.0...v6.3.1)

---
updated-dependencies:
- dependency-name: semver
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-11 10:46:20 +02:00
Kris 00e5bc1614 DEV: remove old shadow definition (#168) 2023-06-15 10:18:04 -04:00
Isaac Janzen e365ced531 DEV: Use angle bracket syntax (#167) 2023-05-28 09:32:27 -05:00
Discourse Translator Bot bf41ea9aa7 Update translations (#166) 2023-05-23 21:23:39 +02:00
Discourse Translator Bot 81205cebaf Update translations (#165) 2023-05-16 19:39:51 +02:00
Mark Reeves 49ddc757c9 Make country names translatable (#164) 2023-05-12 08:55:46 -04:00
Discourse Translator Bot d6337e559a Update translations (#163) 2023-05-10 00:48:25 +02:00
Mark Reeves a6a0cd72c8 FIX: A few more syntax fixes for button actions (#162) 2023-05-08 10:37:37 -04:00
Mark Reeves 7bc34bbbb4 FIX: A few more syntax fixes for button actions (#162) 2023-05-08 10:36:11 -04:00
Mark Reeves 803bba7938 FEATURE: Capture cardholder address fields for Stripe customer (#161)
- Adds the following fields to the subscription payment form:
  - Cardholder Name
  - Country
  - Postal Code
  - Address Line 1
  - City
  - State or Province
- Stripe recommends Cardholder Name & Country for verification; Cardholder Name, Country, and State/Province for US/Canada selections are required fields
- All fields are passed to Stripe for verification on submit
- Fields are also captured on the customer record in Stripe, under Billing Details
2023-05-05 13:20:35 -04:00
Selase Krakani 2babb43ffb FEATURE: Skip product listing if only one product is present (#160)
At the moment, paying for a product typically involves first clicking
the subscribe link added to the navigation bar, selecting a product and
then adding card details to to make the purchase.

This change skips the product selection step if the site has only one
product.
2023-05-04 13:52:04 +00:00
Mark Reeves 9aa4a44b2a FIX: Fix syntax for some buttons following recent updates (#159) 2023-05-03 15:50:20 -04:00
Isaac Janzen c06ae37048 DEV: Use angle bracket syntax (#158) 2023-05-02 09:24:28 -05:00
Discourse Translator Bot 45b7eb6e6c Update translations (#155) 2023-02-21 17:13:37 +01:00
Discourse Translator Bot ee9741aefa Update translations (#154) 2023-02-15 10:53:27 +01:00
Rafael dos Santos Silva bb99a9a66a Revert "DEV: Dart Sass deprecation (#150)" (#151)
This reverts commit 1834095b23.
2023-02-07 18:33:31 -03:00
Rafael dos Santos Silva 1834095b23 DEV: Dart Sass deprecation (#150) 2023-02-07 16:25:47 -03:00
Discourse Translator Bot 8dfdca567d Update translations (#149) 2023-01-24 16:31:56 +01:00
Discourse Translator Bot d4f0b9a43c Update translations (#147) 2023-01-10 20:56:53 +01:00
discoursebotanddiscoursebuild 3051bceb8f DEV: Update CI workflows (#148)
Co-authored-by: discoursebuild <[email protected]>
2023-01-10 19:06:12 +00:00
David Taylor 7fb0936e5c DEV: Update eslint-config-discourse, use prettier for hbs (#146) 2023-01-04 13:39:58 +01:00
dependabot[bot] fb072aa851 Bump json5 from 2.2.1 to 2.2.2 (#145)
Bumps [json5](https://github.com/json5/json5) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v2.2.1...v2.2.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-29 12:25:51 -05:00
David Taylor 4e1a17c40c DEV: Introduce syntax_tree for ruby formatting (#144) 2022-12-29 13:35:06 +01:00
Discourse Translator Bot cbbc23fd5a Update translations (#143) 2022-12-13 17:15:12 +01:00
Discourse Translator Bot e917c3bca6 Update translations (#142) 2022-11-29 15:39:40 +01:00
discoursebotanddiscoursebuild 425fbc0af1 DEV: Update CI workflows (#141)
Co-authored-by: discoursebuild <[email protected]>
2022-11-07 14:21:36 -06:00
Discourse Translator Bot c0b7aa65d7 Update translations (#139) 2022-11-02 15:14:51 +01:00
Jarek Radosz d59a283fd2 DEV: Fix mocha deprecations (#140)
It now supports strict keyword argument matching by default.
2022-11-02 10:47:35 +01:00
Discourse Translator Bot 1f9907d9e7 Update translations (#138) 2022-10-19 15:19:40 +02:00
Penar Musaraj 550e8347e3 DEV: Refactor bootbox dialogs (#137) 2022-10-17 14:42:40 -04:00
David Taylor c93be7632c DEV: Bump rubocop-discourse (#136) 2022-10-04 09:18:34 +01:00
Bianca Nenciu c00e539304 UX: Make styling consistent with everything else (#134) 2022-10-03 16:14:32 +03:00
Bianca Nenciu a58743e376 FIX: Simplify Stripe webhook handler (#135)
The 'customer.subscription.created' webhooks were not handled at all and
the logic was overcomplicated.
2022-10-03 13:33:50 +03:00
Angus McLeod aba86e5e25 FIX: Promo code placeholder (#133) 2022-09-23 10:37:28 +02:00
Discourse Translator Bot 827f9df444 Update translations (#132) 2022-09-20 15:52:53 +02:00
Penar Musaraj 39b7253c08 DEV: Refactor payment plan buttons (#131) 2022-09-14 18:32:22 -04:00
Blake Erickson a78b92ddcc FEATURE: Add quick access button to user profile dropdown (#130) 2022-08-10 13:05:54 +01:00
Discourse Translator Bot e48b817e8e Update translations (#128) 2022-07-12 17:26:08 +02:00
Faizaan GaganandBlake Erickson 945af4f140 FIX: Allow user to update card details for recurring subscriptions (#123)
* add new route for card update

* create backend route

* update label

* basic functionality working

* ran rubocop

* added rspec tests for functionality

* make payment_method param compulsory

* fixed js linting

* improve client side error handling

* improve server side error handling

* improved update card page UI

* improve button UI for user subscriptions page

* give feedback to user about save status

* remove heading from last column

* fix padding on edit/delete buttons for update table

Co-authored-by: Blake Erickson <[email protected]>
2022-07-05 19:53:27 -06:00
David Taylor d4d92d9653 DEV: Use closure actions (#127) 2022-07-06 00:20:12 +01:00
Faizaan Gagan 392b88265b FIX: set Discourse consistent styles on Stripe card element (#124)
* FIX: set Discourse consistent styles on Stripe card element

* use correct placeholder color

* applied prettier

* mock update method for cardElement

* minor formatting
2022-06-24 21:18:38 +05:30
Blake Erickson 8762d6742e FIX: Import script confirmation flag logic (#126)
The import script has a prompt asking if this is a "procourse" import or
not, but the logic for handling the answer is backwards and will
incorrectly set the `procourse_import` flag to `true`.

This commit also fixes a minor typo.
2022-06-22 17:51:14 -06:00
Discourse Translator Bot 353898a6b4 Update translations (#125) 2022-06-21 15:29:16 +02:00
discoursebotanddiscoursebuild 97caad7205 DEV: Update CI workflows (#122)
Co-authored-by: discoursebuild <[email protected]>
2022-06-17 22:22:31 +02:00
275 changed files with 15579 additions and 6306 deletions
+7
View File
@@ -0,0 +1,7 @@
< 3.5.0.beta5-dev: 7309c937dddb1933ac66aa2d3e13d071060d30ff
< 3.5.0.beta1-dev: b4f7be4274ada9400238f8692e705669034c1709
< 3.4.0.beta2-dev: 692f410ab59e329d0aaf537cd772698f83c32a8a
< 3.4.0.beta1-dev: bb96d77056a9ef842e01e46fd980320d427073ad
< 3.3.0.beta1-dev: 0dcf17de59bdf82c0233bb630dad0ce407d1ae76
3.1.999: c4f8220a5572ef9a42a9a71500e001d0df206c43
2.9.0.beta9: 39b7253c082270e33839f4b0821fbf75abfdd826
-6
View File
@@ -1,6 +0,0 @@
{
"globals": {
"Stripe": true
},
"extends": "eslint-config-discourse"
}
+11
View File
@@ -0,0 +1,11 @@
name: Discourse Plugin
on:
push:
branches:
- main
pull_request:
jobs:
ci:
uses: discourse/.github/.github/workflows/discourse-plugin.yml@v1
-57
View File
@@ -1,57 +0,0 @@
name: Linting
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-linting-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: 16
cache: yarn
- name: Yarn install
run: yarn install
- name: Set up ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 2.7
bundler-cache: true
- name: ESLint
if: ${{ always() }}
run: yarn eslint --ext .js,.js.es6 --no-error-on-unmatched-pattern {test,assets}/javascripts
- name: Prettier
if: ${{ always() }}
shell: bash
run: |
yarn prettier -v
if [ 0 -lt $(find assets -type f \( -name "*.scss" -or -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "assets/**/*.{scss,js,es6}"
fi
if [ 0 -lt $(find test -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
yarn prettier --list-different "test/**/*.{js,es6}"
fi
- name: Ember template lint
if: ${{ always() }}
run: yarn ember-template-lint --no-error-on-unmatched-pattern assets/javascripts
- name: Rubocop
if: ${{ always() }}
run: bundle exec rubocop .
-156
View File
@@ -1,156 +0,0 @@
name: Plugin Tests
on:
push:
branches:
- main
pull_request:
concurrency:
group: plugin-tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
cancel-in-progress: true
jobs:
build:
name: ${{ matrix.build_type }}
runs-on: ubuntu-latest
container: discourse/discourse_test:slim${{ startsWith(matrix.build_type, 'frontend') && '-browsers' || '' }}
timeout-minutes: 30
env:
DISCOURSE_HOSTNAME: www.example.com
RUBY_GLOBAL_METHOD_CACHE_SIZE: 131072
RAILS_ENV: test
PGUSER: discourse
PGPASSWORD: discourse
strategy:
fail-fast: false
matrix:
build_type: ["backend", "frontend-legacy", "frontend"]
steps:
- uses: actions/checkout@v3
with:
repository: discourse/discourse
fetch-depth: 1
- name: Install plugin
uses: actions/checkout@v3
with:
path: plugins/${{ github.event.repository.name }}
fetch-depth: 1
- name: Setup Git
run: |
git config --global user.email "[email protected]"
git config --global user.name "Discourse CI"
- name: Start redis
run: |
redis-server /etc/redis/redis.conf &
- name: Start Postgres
run: |
chown -R postgres /var/run/postgresql
sudo -E -u postgres script/start_test_db.rb
sudo -u postgres psql -c "CREATE ROLE $PGUSER LOGIN SUPERUSER PASSWORD '$PGPASSWORD';"
- name: Bundler cache
uses: actions/cache@v3
with:
path: vendor/bundle
key: ${{ runner.os }}-gem-${{ hashFiles('**/Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-gem-
- name: Setup gems
run: |
gem install bundler --conservative -v $(awk '/BUNDLED WITH/ { getline; gsub(/ /,""); print $0 }' Gemfile.lock)
bundle config --local path vendor/bundle
bundle config --local deployment true
bundle config --local without development
bundle install --jobs 4
bundle clean
- name: Lint English locale
if: matrix.build_type == 'backend'
run: bundle exec ruby script/i18n_lint.rb "plugins/${{ github.event.repository.name }}/locales/{client,server}.en.yml"
- name: Get yarn cache directory
id: yarn-cache-dir
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Yarn cache
uses: actions/cache@v3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Yarn install
run: yarn install
- name: Fetch app state cache
uses: actions/cache@v3
id: app-cache
with:
path: tmp/app-cache
key: >-
${{ hashFiles('.github/workflows/tests.yml') }}-
${{ hashFiles('db/**/*', 'plugins/**/db/**/*') }}-
- name: Restore database from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: psql -f tmp/app-cache/cache.sql postgres
- name: Restore uploads from cache
if: steps.app-cache.outputs.cache-hit == 'true'
run: rm -rf public/uploads && cp -r tmp/app-cache/uploads public/uploads
- name: Create and migrate database
if: steps.app-cache.outputs.cache-hit != 'true'
run: |
bin/rake db:create
bin/rake db:migrate
- name: Dump database for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: mkdir -p tmp/app-cache && pg_dumpall > tmp/app-cache/cache.sql
- name: Dump uploads for cache
if: steps.app-cache.outputs.cache-hit != 'true'
run: rm -rf tmp/app-cache/uploads && cp -r public/uploads tmp/app-cache/uploads
- name: Check spec existence
id: check_spec
shell: bash
run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/spec -type f -name "*.rb" 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true"
fi
- name: Plugin RSpec
if: matrix.build_type == 'backend' && steps.check_spec.outputs.files_exist == 'true'
run: bin/rake plugin:spec[${{ github.event.repository.name }}]
- name: Check qunit existence
id: check_qunit
shell: bash
run: |
if [ 0 -lt $(find plugins/${{ github.event.repository.name }}/test/javascripts -type f \( -name "*.js" -or -name "*.es6" \) 2> /dev/null | wc -l) ]; then
echo "::set-output name=files_exist::true"
fi
- name: Plugin QUnit
if: matrix.build_type == 'frontend-legacy' && steps.check_qunit.outputs.files_exist == 'true'
run: QUNIT_EMBER_CLI=0 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 10
- name: Plugin QUnit (Ember CLI)
if: matrix.build_type == 'frontend' && steps.check_qunit.outputs.files_exist == 'true'
run: QUNIT_EMBER_CLI=1 bundle exec rake plugin:qunit['${{ github.event.repository.name }}','1200000']
timeout-minutes: 10
+2
View File
@@ -0,0 +1,2 @@
engine-strict = true
auto-install-peers = false
-1
View File
@@ -1 +0,0 @@
{}
+1
View File
@@ -0,0 +1 @@
module.exports = require("@discourse/lint-configs/prettier");
+1 -1
View File
@@ -1,5 +1,5 @@
inherit_gem:
rubocop-discourse: default.yml
rubocop-discourse: stree-compat.yml
AllCops:
Exclude:
- "gems/**/*"
+2
View File
@@ -0,0 +1,2 @@
--print-width=100
--plugins=plugin/trailing_comma
+1
View File
@@ -0,0 +1 @@
module.exports = require("@discourse/lint-configs/template-lint");
-4
View File
@@ -1,4 +0,0 @@
module.exports = {
plugins: ["ember-template-lint-plugin-discourse"],
extends: "discourse:recommended",
};
+4 -3
View File
@@ -1,8 +1,9 @@
# frozen_string_literal: true
source 'https://rubygems.org'
source "https://rubygems.org"
group :development do
gem 'translations-manager', git: 'https://github.com/discourse/translations-manager.git'
gem 'rubocop-discourse'
gem "translations-manager", git: "https://github.com/discourse/translations-manager.git"
gem "rubocop-discourse"
gem "syntax_tree"
end
+86 -21
View File
@@ -7,38 +7,103 @@ GIT
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
parallel (1.22.1)
parser (3.1.2.0)
activesupport (8.0.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.3.1)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
securerandom (>= 0.3)
tzinfo (~> 2.0, >= 2.0.5)
uri (>= 0.13.1)
ast (2.4.3)
base64 (0.3.0)
benchmark (0.4.1)
bigdecimal (3.2.0)
concurrent-ruby (1.3.5)
connection_pool (2.5.3)
drb (2.2.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
json (2.12.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
logger (1.7.0)
minitest (5.25.5)
parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
prettier_print (1.2.1)
prism (1.4.0)
racc (1.8.1)
rack (3.1.16)
rainbow (3.1.1)
regexp_parser (2.5.0)
rexml (3.2.5)
rubocop (1.30.1)
regexp_parser (2.10.0)
rubocop (1.75.8)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.1.0.0)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.18.0, < 2.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.44.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.18.0)
parser (>= 3.1.1.0)
rubocop-discourse (2.5.0)
rubocop (>= 1.1.0)
rubocop-rspec (>= 2.0.0)
rubocop-rspec (2.11.1)
rubocop (~> 1.19)
ruby-progressbar (1.11.0)
unicode-display_width (2.1.0)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.44.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-capybara (2.22.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-discourse (3.12.1)
activesupport (>= 6.1)
lint_roller (>= 1.1.0)
rubocop (>= 1.73.2)
rubocop-capybara (>= 2.22.0)
rubocop-factory_bot (>= 2.27.0)
rubocop-rails (>= 2.30.3)
rubocop-rspec (>= 3.0.1)
rubocop-rspec_rails (>= 2.31.0)
rubocop-factory_bot (2.27.1)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rails (2.32.0)
activesupport (>= 4.2.0)
lint_roller (~> 1.1)
rack (>= 1.1)
rubocop (>= 1.75.0, < 2.0)
rubocop-ast (>= 1.44.0, < 2.0)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec_rails (2.31.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-rspec (~> 3.5)
ruby-progressbar (1.13.0)
securerandom (0.4.1)
syntax_tree (6.2.0)
prettier_print (>= 1.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
uri (1.0.3)
PLATFORMS
ruby
DEPENDENCIES
rubocop-discourse
syntax_tree
translations-manager!
BUNDLED WITH
2.2.19
2.6.9
+3
View File
@@ -1,5 +1,8 @@
# Discourse Subscriptions Plugin
> [!IMPORTANT]
> This plugin has now been bundled into Discourse core. See: https://meta.discourse.org/t/bundling-more-popular-plugins-with-discourse-core/373574
Discourse Subscriptions allows site owners to sell recurring and one-time purchase subscriptions that grant access to a group on a Discourse instance.
For more information, please see: https://meta.discourse.org/t/discourse-subscriptions-plugin/140818
+2 -1
View File
@@ -9,7 +9,8 @@ module DiscourseSubscriptions
end
def is_stripe_configured?
SiteSetting.discourse_subscriptions_public_key.present? && SiteSetting.discourse_subscriptions_secret_key.present?
SiteSetting.discourse_subscriptions_public_key.present? &&
SiteSetting.discourse_subscriptions_secret_key.present?
end
end
end
@@ -5,6 +5,9 @@ module DiscourseSubscriptions
class CouponsController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
def index
@@ -18,23 +21,24 @@ module DiscourseSubscriptions
end
def create
params.require([:promo, :discount_type, :discount, :active])
params.require(%i[promo discount_type discount active])
begin
coupon_params = {
duration: 'forever',
}
coupon_params = { duration: "forever" }
case params[:discount_type]
when 'amount'
when "amount"
coupon_params[:amount_off] = params[:discount].to_i * 100
coupon_params[:currency] = SiteSetting.discourse_subscriptions_currency
when 'percent'
when "percent"
coupon_params[:percent_off] = params[:discount]
end
coupon = ::Stripe::Coupon.create(coupon_params)
promo_code = ::Stripe::PromotionCode.create({ coupon: coupon[:id], code: params[:promo] }) if coupon.present?
promo_code =
::Stripe::PromotionCode.create(
{ coupon: coupon[:id], code: params[:promo] },
) if coupon.present?
render_json_dump promo_code
rescue ::Stripe::InvalidRequestError => e
@@ -43,14 +47,9 @@ module DiscourseSubscriptions
end
def update
params.require([:id, :active])
params.require(%i[id active])
begin
promo_code = ::Stripe::PromotionCode.update(
params[:id],
{
active: params[:active]
}
)
promo_code = ::Stripe::PromotionCode.update(params[:id], { active: params[:active] })
render_json_dump promo_code
rescue ::Stripe::InvalidRequestError => e
@@ -5,6 +5,8 @@ module DiscourseSubscriptions
class PlansController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
def index
@@ -27,15 +29,11 @@ module DiscourseSubscriptions
active: params[:active],
metadata: {
group_name: params[:metadata][:group_name],
trial_period_days: params[:trial_period_days]
}
trial_period_days: params[:trial_period_days],
},
}
if params[:type] == 'recurring'
price_object[:recurring] = {
interval: params[:interval]
}
end
price_object[:recurring] = { interval: params[:interval] } if params[:type] == "recurring"
plan = ::Stripe::Price.create(price_object)
@@ -56,14 +54,16 @@ module DiscourseSubscriptions
end
interval = nil
if plan[:recurring] && plan[:recurring][:interval]
interval = plan[:recurring][:interval]
end
interval = plan[:recurring][:interval] if plan[:recurring] && plan[:recurring][:interval]
serialized = plan.to_h.merge(trial_period_days: trial_days, currency: plan[:currency].upcase, interval: interval)
serialized =
plan.to_h.merge(
trial_period_days: trial_days,
currency: plan[:currency].upcase,
interval: interval,
)
render_json_dump serialized
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -71,15 +71,16 @@ module DiscourseSubscriptions
def update
begin
plan = ::Stripe::Price.update(
params[:id],
nickname: params[:nickname],
active: params[:active],
metadata: {
group_name: params[:metadata][:group_name],
trial_period_days: params[:trial_period_days]
}
)
plan =
::Stripe::Price.update(
params[:id],
nickname: params[:nickname],
active: params[:active],
metadata: {
group_name: params[:metadata][:group_name],
trial_period_days: params[:trial_period_days],
},
)
render_json_dump plan
rescue ::Stripe::InvalidRequestError => e
@@ -5,6 +5,8 @@ module DiscourseSubscriptions
class ProductsController < ::Admin::AdminController
include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
def index
@@ -27,20 +29,15 @@ module DiscourseSubscriptions
def create
begin
create_params = product_params.merge!(type: 'service')
create_params = product_params.merge!(type: "service")
if params[:statement_descriptor].blank?
create_params.except!(:statement_descriptor)
end
create_params.except!(:statement_descriptor) if params[:statement_descriptor].blank?
product = ::Stripe::Product.create(create_params)
Product.create(
external_id: product[:id]
)
Product.create(external_id: product[:id])
render_json_dump product
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -51,7 +48,6 @@ module DiscourseSubscriptions
product = ::Stripe::Product.retrieve(params[:id])
render_json_dump product
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -59,13 +55,9 @@ module DiscourseSubscriptions
def update
begin
product = ::Stripe::Product.update(
params[:id],
product_params
)
product = ::Stripe::Product.update(params[:id], product_params)
render_json_dump product
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -78,7 +70,6 @@ module DiscourseSubscriptions
Product.delete_by(external_id: params[:id])
render_json_dump product
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -95,8 +86,8 @@ module DiscourseSubscriptions
statement_descriptor: params[:statement_descriptor],
metadata: {
description: params.dig(:metadata, :description),
repurchaseable: params.dig(:metadata, :repurchaseable)
}
repurchaseable: params.dig(:metadata, :repurchaseable),
},
}
end
end
@@ -3,6 +3,8 @@
module DiscourseSubscriptions
module Admin
class SubscriptionsController < ::Admin::AdminController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group
before_action :set_api_key
@@ -16,20 +18,23 @@ module DiscourseSubscriptions
has_more: false,
data: [],
length: 0,
last_record: params[:last_record]
last_record: params[:last_record],
}
if subscription_ids.present? && is_stripe_configured?
while subscriptions[:length] < PAGE_LIMIT
current_set = get_subscriptions(subscriptions[:last_record])
until valid_subscriptions = find_valid_subscriptions(current_set[:data], subscription_ids) do
until valid_subscriptions =
find_valid_subscriptions(current_set[:data], subscription_ids)
current_set = get_subscriptions(current_set[:data].last)
break if current_set[:has_more] == false
end
subscriptions[:data] = subscriptions[:data].concat(valid_subscriptions.to_a)
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[:data].present?
subscriptions[:last_record] = current_set[:data].last[:id] if current_set[
:data
].present?
subscriptions[:length] = subscriptions[:data].length
subscriptions[:has_more] = current_set[:has_more]
break if subscriptions[:has_more] == false
@@ -48,24 +53,21 @@ module DiscourseSubscriptions
params.require(:id)
begin
refund_subscription(params[:id]) if params[:refund]
subscription = ::Stripe::Subscription.delete(params[:id])
subscription = ::Stripe::Subscription.cancel(params[:id])
customer = Customer.find_by(
product_id: subscription[:plan][:product],
customer_id: subscription[:customer]
)
Subscription.delete_by(external_id: params[:id])
customer =
Customer.find_by(
product_id: subscription[:plan][:product],
customer_id: subscription[:customer],
)
if customer
user = ::User.find(customer.user_id)
customer.delete
group = plan_group(subscription[:plan])
group.remove(user) if group
end
render_json_dump subscription
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -74,7 +76,12 @@ module DiscourseSubscriptions
private
def get_subscriptions(start)
::Stripe::Subscription.list(expand: ['data.plan.product'], limit: PAGE_LIMIT, starting_after: start)
::Stripe::Subscription.list(
expand: ["data.plan.product"],
limit: PAGE_LIMIT,
starting_after: start,
status: "all",
)
end
def find_valid_subscriptions(data, ids)
@@ -85,11 +92,11 @@ module DiscourseSubscriptions
# this will only refund the most recent subscription payment
def refund_subscription(subscription_id)
subscription = ::Stripe::Subscription.retrieve(subscription_id)
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[:latest_invoice]
invoice = ::Stripe::Invoice.retrieve(subscription[:latest_invoice]) if subscription[
:latest_invoice
]
payment_intent = invoice[:payment_intent] if invoice[:payment_intent]
refund = ::Stripe::Refund.create({
payment_intent: payment_intent,
})
refund = ::Stripe::Refund.create({ payment_intent: payment_intent })
end
end
end
@@ -2,6 +2,8 @@
module DiscourseSubscriptions
class AdminController < ::Admin::AdminController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
def index
head 200
end
@@ -4,7 +4,12 @@ module DiscourseSubscriptions
class HooksController < ::ApplicationController
include DiscourseSubscriptions::Group
include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
layout false
before_action :set_api_key
skip_before_action :check_xhr
skip_before_action :redirect_to_login_if_required
skip_before_action :verify_authenticity_token, only: [:create]
@@ -12,33 +17,109 @@ module DiscourseSubscriptions
def create
begin
payload = request.body.read
sig_header = request.env['HTTP_STRIPE_SIGNATURE']
sig_header = request.env["HTTP_STRIPE_SIGNATURE"]
webhook_secret = SiteSetting.discourse_subscriptions_webhook_secret
event = ::Stripe::Webhook.construct_event(payload, sig_header, webhook_secret)
rescue JSON::ParserError => e
render_json_error e.message
return
return render_json_error e.message
rescue ::Stripe::SignatureVerificationError => e
render_json_error e.message
return
return render_json_error e.message
end
case event[:type]
when 'customer.subscription.updated'
customer = Customer.find_by(
customer_id: event[:data][:object][:customer],
product_id: event[:data][:object][:plan][:product]
)
when "checkout.session.completed"
checkout_session = event[:data][:object]
if customer && subscription_completion?(event)
user = ::User.find(customer.user_id)
group = plan_group(event[:data][:object][:plan])
group.add(user) if group
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("checkout.session.completed data: #{checkout_session}")
end
email = checkout_session[:customer_email]
return head 200 if checkout_session[:status] != "complete"
return render_json_error "email not found" if !email
if checkout_session[:customer].nil?
customer = ::Stripe::Customer.create({ email: email })
customer_id = customer[:id]
else
customer_id = checkout_session[:customer]
end
when 'customer.subscription.deleted'
delete_subscription(event)
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Looking up user with email: #{email}")
end
user = ::User.find_by_username_or_email(email)
return render_json_error "user not found" if !user
discourse_customer = Customer.create(user_id: user.id, customer_id: customer_id)
subscription = checkout_session[:subscription]
if subscription.present?
Subscription.create(customer_id: discourse_customer.id, external_id: subscription)
end
line_items =
::Stripe::Checkout::Session.list_line_items(checkout_session[:id], { limit: 1 })
item = line_items[:data].first
group = plan_group(item[:price])
group.add(user) unless group.nil?
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Line item with group name meta data: #{item[:price]}")
if group.nil?
Rails.logger.warn("Group not found or not listed in metadata!")
else
Rails.logger.warn("Group: #{group.name}")
end
end
discourse_customer.product_id = item[:price][:product]
discourse_customer.save!
if !subscription.nil?
::Stripe::Subscription.update(
subscription,
{ metadata: { user_id: user.id, username: user.username } },
)
end
when "customer.subscription.created"
when "customer.subscription.updated"
subscription = event[:data][:object]
status = subscription[:status]
return head 200 if !%w[complete active].include?(status)
customer = find_active_customer(subscription[:customer], subscription[:plan][:product])
return render_json_error "customer not found" if !customer
update_status(customer.id, subscription[:id], status)
user = ::User.find_by(id: customer.user_id)
return render_json_error "user not found" if !user
if group = plan_group(subscription[:plan])
group.add(user)
end
when "customer.subscription.deleted"
subscription = event[:data][:object]
customer = find_active_customer(subscription[:customer], subscription[:plan][:product])
return render_json_error "customer not found" if !customer
update_status(customer.id, subscription[:id], subscription[:status])
user = ::User.find(customer.user_id)
return render_json_error "user not found" if !user
if group = plan_group(subscription[:plan])
group.remove(user)
end
end
head 200
@@ -46,37 +127,22 @@ module DiscourseSubscriptions
private
def subscription_completion?(event)
subscription_complete?(event) && previously_incomplete?(event)
def update_status(customer_id, subscription_id, status)
discourse_subscription =
Subscription.find_by(customer_id: customer_id, external_id: subscription_id)
discourse_subscription.update(status: status) if discourse_subscription
end
def subscription_complete?(event)
event && event[:data] && event[:data][:object] && event[:data][:object][:status] && event[:data][:object][:status] == 'complete'
end
def previously_incomplete?(event)
event && event[:data] && event[:data][:previous_attributes] && event[:data][:previous_attributes][:status] && event[:data][:previous_attributes][:status] == 'incomplete'
end
def delete_subscription(event)
customer = Customer.find_by(
customer_id: event[:data][:object][:customer],
product_id: event[:data][:object][:plan][:product]
)
if customer
sub_model = Subscription.find_by(
customer_id: customer.id,
external_id: [:id]
def find_active_customer(customer_id, product_id)
Customer
.joins(:subscriptions)
.where(customer_id: customer_id, product_id: product_id)
.where(
Subscription.arel_table[:status].eq(nil).or(
Subscription.arel_table[:status].not_eq("canceled"),
),
)
sub_model.delete if sub_model
user = ::User.find(customer.user_id)
customer.delete
group = plan_group(event[:data][:object][:plan])
group.remove(user) if group
end
.first
end
end
end
@@ -0,0 +1,11 @@
# frozen_string_literal: true
module DiscourseSubscriptions
class PricingtableController < ::ApplicationController
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
def index
head 200
end
end
end
@@ -4,8 +4,11 @@ module DiscourseSubscriptions
class SubscribeController < ::ApplicationController
include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
requires_login except: [:index, :contributors, :show]
requires_login except: %i[index contributors show]
def index
begin
@@ -13,19 +16,12 @@ module DiscourseSubscriptions
products = []
if product_ids.present? && is_stripe_configured?
response = ::Stripe::Product.list({
ids: product_ids,
active: true
})
products = response[:data].map do |p|
serialize_product(p)
end
response = ::Stripe::Product.list({ ids: product_ids, active: true })
products = response[:data].map { |p| serialize_product(p) }
end
render_json_dump products
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -36,7 +32,11 @@ module DiscourseSubscriptions
contributor_ids = Set.new
campaign_product = SiteSetting.discourse_subscriptions_campaign_product
campaign_product.present? ? contributor_ids.merge(Customer.where(product_id: campaign_product).last(5).pluck(:user_id)) : contributor_ids.merge(Customer.last(5).pluck(:user_id))
if campaign_product.present?
contributor_ids.merge(Customer.where(product_id: campaign_product).last(5).pluck(:user_id))
else
contributor_ids.merge(Customer.last(5).pluck(:user_id))
end
contributors = ::User.where(id: contributor_ids)
@@ -49,10 +49,7 @@ module DiscourseSubscriptions
product = ::Stripe::Product.retrieve(params[:id])
plans = ::Stripe::Price.list(active: true, product: params[:id])
response = {
product: serialize_product(product),
plans: serialize_plans(plans)
}
response = { product: serialize_product(product), plans: serialize_plans(plans) }
render_json_dump response
rescue ::Stripe::InvalidRequestError => e
@@ -61,47 +58,77 @@ module DiscourseSubscriptions
end
def create
params.require([:source, :plan])
params.require(%i[source plan])
begin
customer = find_or_create_customer(params[:source])
customer =
find_or_create_customer(
params[:source],
params[:cardholder_name],
params[:cardholder_address],
)
plan = ::Stripe::Price.retrieve(params[:plan])
if params[:promo].present?
promo_code = ::Stripe::PromotionCode.list({ code: params[:promo] })
promo_code = promo_code[:data][0] # we assume promo codes have a unique name
return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon") if promo_code.blank?
if promo_code.blank?
return render_json_error I18n.t("js.discourse_subscriptions.subscribe.invalid_coupon")
end
end
recurring_plan = plan[:type] == 'recurring'
recurring_plan = plan[:type] == "recurring"
if recurring_plan
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] && plan[:metadata][:trial_period_days]
trial_days = plan[:metadata][:trial_period_days] if plan[:metadata] &&
plan[:metadata][:trial_period_days]
promo_code_id = promo_code[:id] if promo_code
transaction = ::Stripe::Subscription.create(
subscription_params = {
customer: customer[:id],
items: [{ price: params[:plan] }],
metadata: metadata_user,
trial_period_days: trial_days,
promotion_code: promo_code_id
)
promotion_code: promo_code_id,
}
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[:status] == 'incomplete'
if SiteSetting.discourse_subscriptions_enable_automatic_tax
subscription_params[:automatic_tax] = { enabled: true }
end
transaction = ::Stripe::Subscription.create(subscription_params)
payment_intent = retrieve_payment_intent(transaction[:latest_invoice]) if transaction[
:status
] == "incomplete"
else
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] && promo_code[:coupon][:id]
invoice_item = ::Stripe::InvoiceItem.create(
customer: customer[:id],
price: params[:plan],
discounts: [{ coupon: coupon_id }]
)
invoice = ::Stripe::Invoice.create(
customer: customer[:id]
)
coupon_id = promo_code[:coupon][:id] if promo_code && promo_code[:coupon] &&
promo_code[:coupon][:id]
invoice_params = { customer: customer[:id] }
if SiteSetting.discourse_subscriptions_enable_automatic_tax
invoice_params[:automatic_tax] = { enabled: true }
end
invoice = ::Stripe::Invoice.create(invoice_params)
invoice_item =
::Stripe::InvoiceItem.create(
customer: customer[:id],
price: params[:plan],
discounts: [{ coupon: coupon_id }],
invoice: invoice[:id],
)
transaction = ::Stripe::Invoice.finalize_invoice(invoice[:id])
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] == 'open'
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] == 'successful'
payment_intent = retrieve_payment_intent(transaction[:id]) if transaction[:status] ==
"open"
if payment_intent.nil?
return(
render_json_error I18n.t("js.discourse_subscriptions.subscribe.transaction_error")
)
end
transaction = ::Stripe::Invoice.pay(invoice[:id]) if payment_intent[:status] ==
"successful"
end
finalize_transaction(transaction, plan) if transaction_ok(transaction)
@@ -115,7 +142,7 @@ module DiscourseSubscriptions
end
def finalize
params.require([:plan, :transaction])
params.require(%i[plan transaction])
begin
price = ::Stripe::Price.retrieve(params[:plan])
transaction = retrieve_transaction(params[:transaction])
@@ -132,16 +159,18 @@ module DiscourseSubscriptions
group.add(current_user) if group
customer = Customer.create(
user_id: current_user.id,
customer_id: transaction[:customer],
product_id: plan[:product]
)
customer =
Customer.create(
user_id: current_user.id,
customer_id: transaction[:customer],
product_id: plan[:product],
)
if transaction[:object] == 'subscription'
if transaction[:object] == "subscription"
Subscription.create(
customer_id: customer.id,
external_id: transaction[:id]
external_id: transaction[:id],
status: transaction[:status],
)
end
end
@@ -154,7 +183,7 @@ module DiscourseSubscriptions
name: product[:name],
description: PrettyText.cook(product[:metadata][:description]),
subscribed: current_user_products.include?(product[:id]),
repurchaseable: product[:metadata][:repurchaseable]
repurchaseable: product[:metadata][:repurchaseable],
}
end
@@ -162,26 +191,49 @@ module DiscourseSubscriptions
return [] if current_user.nil?
Customer
.select(:product_id)
.joins(:subscriptions)
.where(user_id: current_user.id)
.map { |c| c.product_id }.compact
.where(
Subscription.arel_table[:status].eq(nil).or(
Subscription.arel_table[:status].not_eq("canceled"),
),
)
.select(:product_id)
.distinct
.pluck(:product_id)
end
def serialize_plans(plans)
plans[:data].map do |plan|
plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring)
end.sort_by { |plan| plan[:amount] }
plans[:data]
.map { |plan| plan.to_h.slice(:id, :unit_amount, :currency, :type, :recurring) }
.sort_by { |plan| plan[:amount] }
end
def find_or_create_customer(source)
def find_or_create_customer(source, cardholder_name = nil, cardholder_address = nil)
customer = Customer.find_by_user_id(current_user.id)
cardholder_address =
(
if cardholder_address.present?
{
line1: cardholder_address[:line1],
city: cardholder_address[:city],
state: cardholder_address[:state],
country: cardholder_address[:country],
postal_code: cardholder_address[:postalCode],
}
else
nil
end
)
if customer.present?
::Stripe::Customer.retrieve(customer.customer_id)
else
::Stripe::Customer.create(
email: current_user.email,
source: source
source: source,
name: cardholder_name,
address: cardholder_address,
)
end
end
@@ -4,6 +4,9 @@ module DiscourseSubscriptions
module User
class PaymentsController < ::ApplicationController
include DiscourseSubscriptions::Stripe
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
requires_login
@@ -22,15 +25,36 @@ module DiscourseSubscriptions
invoices_with_products = parse_invoices(all_invoices, product_ids)
invoice_ids = invoices_with_products.map { |invoice| invoice[:id] }
payments = ::Stripe::PaymentIntent.list(customer: customer_id)
payments_from_invoices = payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
data = data | payments_from_invoices
payments_from_invoices =
payments[:data].select { |payment| invoice_ids.include?(payment[:invoice]) }
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Payments from invoices: #{payments_from_invoices}")
end
# Pricing table one-off purchases do not have invoices
payments_without_invoices =
payments[:data].select { |payment| payment[:invoice].nil? }
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Payments without invoices: #{payments_without_invoices}")
end
data = data | payments_from_invoices | payments_without_invoices
end
end
if SiteSetting.discourse_subscriptions_pricing_table_enabled && current_user.email
related_guest_payments = fetch_guest_payments(current_user.email)
if SiteSetting.discourse_subscriptions_enable_verbose_logging
Rails.logger.warn("Related guest payments: #{related_guest_payments}")
end
data = data | related_guest_payments
end
data = data.sort_by { |pmt| pmt[:created] }.reverse
render_json_dump data
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -39,18 +63,56 @@ module DiscourseSubscriptions
private
def parse_invoices(all_invoices, product_ids)
invoices_with_products = all_invoices[:data].select do |invoice|
all_invoices[:data].select do |invoice|
invoice_lines = invoice[:lines][:data][0] if invoice[:lines] && invoice[:lines][:data]
invoice_product_id = parse_invoice_lines(invoice_lines)
product_ids.include?(invoice_product_id)
if invoice_lines
invoice_product_id = parse_invoice_lines(invoice_lines)
product_ids.include?(invoice_product_id)
end
end
end
def parse_invoice_lines(invoice_lines)
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] && invoice_lines[:price][:product]
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] && invoice_lines[:plan][:product]
invoice_product_id = invoice_lines[:price][:product] if invoice_lines[:price] &&
invoice_lines[:price][:product]
invoice_product_id = invoice_lines[:plan][:product] if invoice_lines[:plan] &&
invoice_lines[:plan][:product]
invoice_product_id
end
def fetch_guest_payments(email)
guest_payments = []
starting_after = nil
begin
loop do
# Fetch charges in batches of 100, using pagination with starting_after
charges =
::Stripe::Charge.list(
limit: 100,
starting_after: starting_after,
expand: ["data.payment_intent"],
)
charges[:data].each do |charge|
# Check if the charge is associated with the given email and has no customer ID
if charge[:billing_details][:email] == email && charge[:customer].nil?
guest_payments << charge
end
end
# Check if there are more charges to fetch
break if charges[:data].count < 100
# Set starting_after to the last charge's ID for the next batch
starting_after = charges[:data].last[:id]
end
rescue ::Stripe::StripeError => e
render_json_error e.message
end
guest_payments
end
end
end
end
@@ -5,6 +5,9 @@ module DiscourseSubscriptions
class SubscriptionsController < ::ApplicationController
include DiscourseSubscriptions::Stripe
include DiscourseSubscriptions::Group
requires_plugin DiscourseSubscriptions::PLUGIN_NAME
before_action :set_api_key
requires_login
@@ -12,36 +15,40 @@ module DiscourseSubscriptions
begin
customer = Customer.where(user_id: current_user.id)
customer_ids = customer.map { |c| c.id } if customer
subscription_ids = Subscription.where("customer_id in (?)", customer_ids).pluck(:external_id) if customer_ids
stripe_customer_ids = customer.map { |c| c.customer_id }.uniq if customer
subscription_ids =
Subscription.where("customer_id in (?)", customer_ids).pluck(
:external_id,
) if customer_ids
subscriptions = []
if subscription_ids
plans = ::Stripe::Price.list(
expand: ['data.product'],
limit: 100
)
prices = []
price_params = { limit: 100, expand: ["data.product"] }
loop do
response = ::Stripe::Price.list(price_params)
prices.concat(response[:data])
break unless response[:has_more]
price_params[:starting_after] = response[:data].last.id
end
all_subscriptions = []
customers = ::Stripe::Customer.list(
email: current_user.email,
expand: ['data.subscriptions']
)
subscriptions = customers[:data].map do |sub_customer|
sub_customer[:subscriptions][:data]
end.flatten(1)
subscriptions = subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
stripe_customer_ids.each do |stripe_customer_id|
customer_subscriptions =
::Stripe::Subscription.list(customer: stripe_customer_id, status: "all")
all_subscriptions.concat(customer_subscriptions[:data])
end
subscriptions = all_subscriptions.select { |sub| subscription_ids.include?(sub[:id]) }
subscriptions.map! do |subscription|
plan = plans[:data].find { |p| p[:id] == subscription[:items][:data][0][:price][:id] }
plan = prices.find { |p| p[:id] == subscription[:items][:data][0][:price][:id] }
subscription.to_h.except!(:plan)
subscription.to_h.merge(plan: plan, product: plan[:product].to_h.slice(:id, :name))
end
end
render_json_dump subscriptions
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
@@ -51,18 +58,41 @@ module DiscourseSubscriptions
# we cancel but don't remove until the end of the period
# full removal is done via webhooks
begin
subscription = ::Stripe::Subscription.update(params[:id], { cancel_at_period_end: true, })
subscription = ::Stripe::Subscription.update(params[:id], { cancel_at_period_end: true })
if subscription
render_json_dump subscription
else
render_json_error I18n.t('discourse_subscriptions.customer_not_found')
render_json_error I18n.t("discourse_subscriptions.customer_not_found")
end
rescue ::Stripe::InvalidRequestError => e
render_json_error e.message
end
end
def update
params.require(:payment_method)
subscription = Subscription.where(external_id: params[:id]).first
begin
attach_method_to_customer(subscription.customer_id, params[:payment_method])
subscription =
::Stripe::Subscription.update(
params[:id],
{ default_payment_method: params[:payment_method] },
)
render json: success_json
rescue ::Stripe::InvalidRequestError
render_json_error I18n.t("discourse_subscriptions.card.invalid")
end
end
private
def attach_method_to_customer(customer_id, method)
customer = Customer.find(customer_id)
::Stripe::PaymentMethod.attach(method, { customer: customer.customer_id })
end
end
end
end
@@ -2,7 +2,6 @@
module ::Jobs
class ManuallyUpdateCampaignData < ::Jobs::Base
def execute(args)
return unless SiteSetting.discourse_subscriptions_campaign_enabled
DiscourseSubscriptions::Campaign.new.refresh_data
@@ -13,3 +13,20 @@ module DiscourseSubscriptions
end
end
end
# == Schema Information
#
# Table name: discourse_subscriptions_customers
#
# id :bigint not null, primary key
# customer_id :string not null
# product_id :string
# user_id :bigint
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_discourse_subscriptions_customers_on_customer_id (customer_id)
# index_discourse_subscriptions_customers_on_user_id (user_id)
#
@@ -4,3 +4,17 @@ module DiscourseSubscriptions
class Product < ActiveRecord::Base
end
end
# == Schema Information
#
# Table name: discourse_subscriptions_products
#
# id :bigint not null, primary key
# external_id :string not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_discourse_subscriptions_products_on_external_id (external_id)
#
@@ -5,3 +5,20 @@ module DiscourseSubscriptions
belongs_to :customer
end
end
# == Schema Information
#
# Table name: discourse_subscriptions_subscriptions
#
# id :bigint not null, primary key
# customer_id :bigint not null
# external_id :string not null
# created_at :datetime not null
# updated_at :datetime not null
# status :string
#
# Indexes
#
# index_discourse_subscriptions_subscriptions_on_customer_id (customer_id)
# index_discourse_subscriptions_subscriptions_on_external_id (external_id)
#

Some files were not shown because too many files have changed in this diff Show More