Compare commits

...

58 Commits

Author SHA1 Message Date
dependabot[bot]
8f592f8004 Bump active_storage_validations from 1.1.4 to 3.0.3
Bumps [active_storage_validations](https://github.com/igorkasyanchuk/active_storage_validations) from 1.1.4 to 3.0.3.
- [Release notes](https://github.com/igorkasyanchuk/active_storage_validations/releases)
- [Changelog](https://github.com/igorkasyanchuk/active_storage_validations/blob/master/CHANGES.md)
- [Commits](https://github.com/igorkasyanchuk/active_storage_validations/commits/3.0.03)

---
updated-dependencies:
- dependency-name: active_storage_validations
  dependency-version: 3.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-08 13:51:23 +00:00
Filipe
80bd6defcb Merge pull request #13789 from prikeshsavla/13537-upgrade-active-storage-validations-gem
Upgraded gem active_storage_validations to 3.0.2 and fixed any upgrade related issues
2026-01-08 13:06:53 +00:00
Filipe
bd367cb154 Merge pull request #13795 from prikeshsavla/13392-fix-producer-name-text-encoding
Fix encoding issue using ng-bind-html
2026-01-08 12:03:00 +00:00
Filipe
d0f48687e2 Merge pull request #13777 from rioug/13481-webhook-payment
Payment status change webhook
2026-01-08 11:26:41 +00:00
Filipe
25063d2c4d Merge pull request #13649 from deivid-rodriguez/fix-removal-flash-message-translations
Improve translations of some flash messages
2026-01-07 16:33:34 +00:00
Maikel
bad04b70a9 Merge pull request #13832 from dacook/pr-template-headings
Increase PR headings to level 2
2026-01-07 17:19:36 +11:00
David Cook
5479572a08 Increase headings to level 2
h4 is rendered as bold text the same size as content.
2026-01-07 17:00:34 +11:00
Gaetan Craig-Riou
06bfd07fec Merge pull request #13824 from openfoodfoundation/dependabot/npm_and_yarn/trix-2.1.16
Bump trix from 2.1.15 to 2.1.16
2026-01-05 11:12:11 +11:00
Maikel
e98cf78b4c Merge pull request #13819 from openfoodfoundation/dependabot/bundler/haml_lint-0.68.0
Bump haml_lint from 0.67.0 to 0.68.0
2026-01-02 16:50:21 +11:00
dependabot[bot]
13229cc0c1 Bump trix from 2.1.15 to 2.1.16
Bumps [trix](https://github.com/basecamp/trix) from 2.1.15 to 2.1.16.
- [Release notes](https://github.com/basecamp/trix/releases)
- [Commits](https://github.com/basecamp/trix/compare/v2.1.15...v2.1.16)

---
updated-dependencies:
- dependency-name: trix
  dependency-version: 2.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-31 09:01:27 +00:00
dependabot[bot]
3173c79e8f Bump haml_lint from 0.67.0 to 0.68.0
Bumps [haml_lint](https://github.com/sds/haml-lint) from 0.67.0 to 0.68.0.
- [Release notes](https://github.com/sds/haml-lint/releases)
- [Changelog](https://github.com/sds/haml-lint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sds/haml-lint/compare/v0.67.0...v0.68.0)

---
updated-dependencies:
- dependency-name: haml_lint
  dependency-version: 0.68.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 09:04:24 +00:00
David Cook
ca14d557c1 Merge pull request #13814 from mkllnk/dfc-events-unauthorised
Correctly respond to unauthorised requests on DFC events endpoint
2025-12-22 10:42:23 +11:00
Gaetan Craig-Riou
59a3a5bd92 Merge pull request #13816 from openfoodfoundation/dependabot/bundler/dotenv-3.2.0
Bump dotenv from 3.1.8 to 3.2.0
2025-12-22 09:44:15 +11:00
dependabot[bot]
a226088f5c Bump dotenv from 3.1.8 to 3.2.0
Bumps [dotenv](https://github.com/bkeepers/dotenv) from 3.1.8 to 3.2.0.
- [Release notes](https://github.com/bkeepers/dotenv/releases)
- [Changelog](https://github.com/bkeepers/dotenv/blob/main/Changelog.md)
- [Commits](https://github.com/bkeepers/dotenv/compare/v3.1.8...v3.2.0)

---
updated-dependencies:
- dependency-name: dotenv
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-19 09:04:48 +00:00
Maikel
c65fcc1072 Merge pull request #13812 from openfoodfoundation/dependabot/bundler/aws-sdk-s3-1.208.0
Bump aws-sdk-s3 from 1.207.0 to 1.208.0
2025-12-19 10:41:27 +11:00
Maikel
3bb68ec07e Merge pull request #13811 from openfoodfoundation/dependabot/bundler/webmock-3.26.1
Bump webmock from 3.25.1 to 3.26.1
2025-12-19 10:39:45 +11:00
Maikel Linke
2c97638aa1 Enhance readability 2025-12-19 10:21:02 +11:00
Maikel Linke
ceee9671d9 Replace method with name conflict causing error 2025-12-19 10:16:46 +11:00
dependabot[bot]
6b494be7ff Bump aws-sdk-s3 from 1.207.0 to 1.208.0
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.207.0 to 1.208.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-version: 1.208.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-18 19:38:08 +00:00
dependabot[bot]
a6855e6bc1 Bump webmock from 3.25.1 to 3.26.1
Bumps [webmock](https://github.com/bblimke/webmock) from 3.25.1 to 3.26.1.
- [Release notes](https://github.com/bblimke/webmock/releases)
- [Changelog](https://github.com/bblimke/webmock/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bblimke/webmock/compare/v3.25.1...v3.26.1)

---
updated-dependencies:
- dependency-name: webmock
  dependency-version: 3.26.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-17 09:01:49 +00:00
Gaetan Craig-Riou
7ca43eb4a1 Merge pull request #13810 from openfoodfoundation/dependabot/bundler/aws-sdk-s3-1.207.0
Bump aws-sdk-s3 from 1.206.0 to 1.207.0
2025-12-17 10:21:50 +11:00
dependabot[bot]
74b5ac559f Bump aws-sdk-s3 from 1.206.0 to 1.207.0
Bumps [aws-sdk-s3](https://github.com/aws/aws-sdk-ruby) from 1.206.0 to 1.207.0.
- [Release notes](https://github.com/aws/aws-sdk-ruby/releases)
- [Changelog](https://github.com/aws/aws-sdk-ruby/blob/version-3/gems/aws-sdk-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-ruby/commits)

---
updated-dependencies:
- dependency-name: aws-sdk-s3
  dependency-version: 1.207.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 09:02:51 +00:00
Maikel
07c236497c Merge pull request #13808 from openfoodfoundation/dependabot/bundler/vcr-6.3.1
Bump vcr from 6.2.0 to 6.3.1
2025-12-16 14:50:27 +11:00
Filipe
caf2ff9bb4 Merge pull request #13752 from deivid-rodriguez/always_generate_button_tags
Always generate `<button>` tags, rather than `<input>` of type "button"
2025-12-15 18:46:38 +00:00
Filipe
1b2a17d7e4 Merge pull request #13754 from deivid-rodriguez/no-changes-after-delete-customer
Properly handle changes in `code` attribute when a customer is deleted
2025-12-15 17:58:04 +00:00
Filipe
ce46115139 Merge pull request #13648 from deivid-rodriguez/improve-enterprise-removal
Improve enterprise removal
2025-12-15 16:54:05 +00:00
dependabot[bot]
9fd2ff7620 Bump vcr from 6.2.0 to 6.3.1
Bumps [vcr](https://github.com/vcr/vcr) from 6.2.0 to 6.3.1.
- [Release notes](https://github.com/vcr/vcr/releases)
- [Changelog](https://github.com/vcr/vcr/blob/master/CHANGELOG.md)
- [Commits](https://github.com/vcr/vcr/compare/v6.2.0...v6.3.1)

---
updated-dependencies:
- dependency-name: vcr
  dependency-version: 6.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 09:02:43 +00:00
Gaetan Craig-Riou
584b976dff Per review, small code improvment 2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
4073238654 Per review, fix test webhook
- only show button for payment status changed webhook
- update translation
2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
d7505bcef4 Add Payments::WebhookPayload to manage payload data
It includes test data so any change in the payload should not affect
the test webhook enpoint functionality
2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
f6a7225c47 Per review, remove the ensure 2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
377f33b64f Use a better selector to pick table row 2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
73b27f14ab Per review, fix comment 2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
0b497fbb77 Fix order payment spec 2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
5e4df41ec8 Add button to send test data to endpoint
It will allow a user to easily test the endpoint
2025-12-10 10:28:12 +11:00
Gaetan Craig-Riou
72085be896 Format account.scss with prettier 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
e0bc8f9cdc Fix webhook endpoints controller spec 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
efcb442a80 Add spec to test notification is triggered 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
a38023475c Trigger payment webhook when a payment status changes
It used ActiveSupport::Notifications and a listener :
StatusChangedListenerService to trigger the WebhookService
2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
4a6ba29b99 Add Payments::WebhookService
It enqueues jobs to post the generated payload to the various configured
webhook endpoints for payment status change
2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
7f961d90c2 Enable active_job.use_big_decimal_serializer
It prevents the following deprecation warning:
DEPRECATION WARNING: Primitive serialization of BigDecimal job arguments is deprecated as it may serialize via .to_s using certain queue adapters.
       Enable config.active_job.use_big_decimal_serializer to use BigDecimalSerializer instead, which will be mandatory in Rails 7.2.
2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
0ac4021729 Update spec to include payment status webhook 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
ac662de789 Fix spec use actual translation 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
23c57cb354 Add UI to manage payment staus webhook endpoint 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
d6ef56af6e Fix existing code to support webhook_type 2025-12-10 10:28:11 +11:00
Gaetan Craig-Riou
059e36318e Add type to WebhookEnpoints
Add migration to update existing endpoint to "order_cycle_opened" type
2025-12-10 10:28:08 +11:00
Prikesh Savla
c01cca33c7 Fix encoding issue for Producer name to allow special characters in the text using ng-bind-html 2025-12-09 11:43:12 +05:30
Prikesh Savla
631306cfb3 Extended imageImport and ImageBuilder to get the content type of the file for the attacment for avoiding issues for files without extensions.
Updated config/locale/en.yml for the active_storage_validations related error messages
2025-12-09 08:06:29 +05:30
Prikesh Savla
f4d59305d7 Upgraded gem active_storage_validations from 1.1.2 to 3.0.2 and fixed any upgrade related issues
Changed all references of processable_image to processable_file which was a breaking change from v1 to v2 https://github.com/igorkasyanchuk/active_storage_validations/tree/3.0.2?tab=readme-ov-file#upgrading-from-1x-to-2x

Also it upgraded the way of validating files from just the file name and content type, so tests also needed to change for file upload checks

Refactored all the similar file image validator content type in Spree::Image::ACCEPTED_CONTENT_TYPES and Updated ImageBuilder.import method to use the url.path when getting filename.
2025-12-08 22:12:01 +05:30
David Rodríguez
c526e72539 Improve enterprise removal (failure case)
Make sure failure to delete due to dependent objects is handled through
activemodel errors and not by rescuing
`ActiveRecord::DeleteRestrictionError` exceptions.

Previously we would display two alert prompts, and we would weirdly
display the content of our 500 error page on top of the screen.

Now, we display a flash error message explaining the reason to fail to
remove it.
2025-11-27 19:10:15 +01:00
David Rodríguez
e217a6fca8 Make enterprise unit specs about removal consistent
And not dependent on implementation details.
2025-11-27 19:09:45 +01:00
David Rodríguez
6aa7ef3c21 Improve enterprise removal (success case)
Make enterprise removal use turbo, which provides the following
benefits:

* More responsive removal since there's no full page reload.
* A success flash message (previously nothing was displayed).
* No double alert prompt.

It also goes in the direction of removing mrujs in favor of turbo.
2025-11-27 19:09:35 +01:00
David Rodríguez
bf0e5c0d44 Let "Tag Rule" and "Voucher" be translated in flash messages 2025-11-26 12:18:06 +01:00
David Rodríguez
6bd2f5af8d Use Spree.t directly for translating the successfully_removed flash message
Since none of the current keys have a `%{resource}` parameter.
2025-11-26 12:18:06 +01:00
David Rodríguez
7bf54088a6 Use Spree.t directly for translating the not_found message
Since none of the current keys interpolate a `%{resource}` parameter.
2025-11-26 12:18:06 +01:00
David Rodríguez
4792040240 Cover tax category removal with a spec 2025-11-26 12:18:05 +01:00
David Rodríguez
dc631026d4 Properly handle changes in code attribute when a customer is deleted
Previously, `null` and empty value would be confused when a customer is
removed, resulting in incorrect pending changes being added, and thus a
"You have unsaved changes" message getting displayed and the save button
not getting disabled.
2025-11-25 07:44:00 +01:00
David Rodríguez
c05532c166 Always generate <button> tags, rather than <input> of type "button" 2025-11-24 12:11:03 +01:00
59 changed files with 869 additions and 201 deletions

View File

@@ -1,4 +1,4 @@
#### What? Why?
## What? Why?
- Closes # <!-- Insert issue number here. -->
@@ -7,7 +7,7 @@
#### What should we test?
## What should we test?
<!-- List which features should be tested and how.
This can be similar to the Steps to Reproduce in the issue.
Also think of other parts of the app which could be affected
@@ -16,7 +16,7 @@
- Visit ... page.
-
#### Release notes
## Release notes
<!-- Please select one for your PR and delete the other. -->
@@ -33,12 +33,12 @@ Changelog Category (reviewers may add a label for the release notes):
The title of the pull request will be included in the release notes.
#### Dependencies
## Dependencies
<!-- Does this PR depend on another one?
Add the link or remove this section. -->
#### Documentation updates
## Documentation updates
<!-- Are there any wiki pages that need updating after merging this PR?
List them here or remove this section. -->

View File

@@ -104,11 +104,12 @@ GEM
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.8.4)
activemodel (>= 3.0)
active_storage_validations (1.1.4)
activejob (>= 5.2.0)
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
active_storage_validations (3.0.3)
activejob (>= 6.1.4)
activemodel (>= 6.1.4)
activestorage (>= 6.1.4)
activesupport (>= 6.1.4)
marcel (>= 1.0.3)
activejob (7.1.6)
activesupport (= 7.1.6)
globalid (>= 0.3.6)
@@ -158,8 +159,8 @@ GEM
zeitwerk (>= 2.4, < 3.0)
acts_as_list (1.0.4)
activerecord (>= 4.2)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
addressable (2.8.8)
public_suffix (>= 2.0.2, < 8.0)
aes_key_wrap (1.1.0)
afm (0.2.2)
angular-rails-templates (1.4.0)
@@ -176,8 +177,8 @@ GEM
ast (2.4.3)
attr_required (1.0.2)
aws-eventstream (1.4.0)
aws-partitions (1.1191.0)
aws-sdk-core (3.239.2)
aws-partitions (1.1196.0)
aws-sdk-core (3.240.0)
aws-eventstream (~> 1, >= 1.3.0)
aws-partitions (~> 1, >= 1.992.0)
aws-sigv4 (~> 1.9)
@@ -188,7 +189,7 @@ GEM
aws-sdk-kms (1.118.0)
aws-sdk-core (~> 3, >= 3.239.1)
aws-sigv4 (~> 1.5)
aws-sdk-s3 (1.206.0)
aws-sdk-s3 (1.208.0)
aws-sdk-core (~> 3, >= 3.234.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.5)
@@ -233,7 +234,7 @@ GEM
marcel (~> 1.0)
nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
cgi (0.5.0)
cgi (0.5.1)
childprocess (5.0.0)
choice (0.2.0)
chronic (0.10.2)
@@ -248,10 +249,10 @@ GEM
combine_pdf (1.0.31)
matrix
ruby-rc4 (>= 0.1.5)
concurrent-ruby (1.3.5)
connection_pool (2.5.5)
concurrent-ruby (1.3.6)
connection_pool (3.0.2)
cookiejar (0.3.4)
crack (1.0.0)
crack (1.0.1)
bigdecimal
rexml
crass (1.0.6)
@@ -289,7 +290,7 @@ GEM
diff-lcs (1.6.2)
digest (3.2.1)
docile (1.4.1)
dotenv (3.1.8)
dotenv (3.2.0)
drb (2.2.3)
em-http-request (1.1.7)
addressable (>= 2.3.4)
@@ -379,7 +380,7 @@ GEM
temple (>= 0.8.2)
thor
tilt
haml_lint (0.67.0)
haml_lint (0.68.0)
haml (>= 5.0)
parallel (~> 1.10)
rainbow
@@ -468,7 +469,7 @@ GEM
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
loofah (2.24.1)
loofah (2.25.0)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
@@ -491,7 +492,8 @@ GEM
logger
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.26.2)
minitest (6.0.1)
prism (~> 1.5)
monetize (1.13.0)
money (~> 6.12)
money (6.16.0)
@@ -513,7 +515,7 @@ GEM
net-protocol
newrelic_rpm (9.24.0)
nio4r (2.7.5)
nokogiri (1.18.10)
nokogiri (1.19.0)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nokogiri-html5-inference (0.3.0)
@@ -576,7 +578,7 @@ GEM
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
prism (1.6.0)
prism (1.7.0)
private_address_check (0.5.0)
pry (0.15.2)
coderay (~> 1.1)
@@ -584,7 +586,7 @@ GEM
psych (5.2.6)
date
stringio
public_suffix (6.0.2)
public_suffix (7.0.0)
puffing-billy (4.0.2)
addressable (~> 2.5)
em-http-request (~> 1.1, >= 1.1.0)
@@ -875,13 +877,13 @@ GEM
faraday (~> 2.0)
faraday-follow_redirects
sysexits (1.2.0)
temple (0.8.2)
temple (0.10.4)
terminal-table (4.0.0)
unicode-display_width (>= 1.1.1, < 4)
thor (1.4.0)
thread-local (1.1.0)
tilt (2.6.1)
timeout (0.4.4)
timeout (0.6.0)
tsort (0.2.0)
ttfunk (1.8.0)
bigdecimal (~> 3.1)
@@ -902,7 +904,7 @@ GEM
simplecov_json_formatter
unicode-display_width (3.2.0)
unicode-emoji (~> 4.1)
unicode-emoji (4.1.0)
unicode-emoji (4.2.0)
uniform_notifier (1.17.0)
uri (1.1.1)
valid_email2 (5.2.3)
@@ -913,7 +915,8 @@ GEM
public_suffix
validates_lengths_from_database (0.8.0)
activerecord (>= 4)
vcr (6.2.0)
vcr (6.3.1)
base64
view_component (4.1.1)
actionview (>= 7.1.0, < 8.2)
activesupport (>= 7.1.0, < 8.2)
@@ -935,7 +938,7 @@ GEM
activesupport
faraday (~> 2.0)
faraday-follow_redirects
webmock (3.25.1)
webmock (3.26.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)

View File

@@ -38,16 +38,13 @@ angular.module("admin.indexUtils").directive "objForUpdate", (switchClass, pendi
# To ensure the customer is still updated, we check on the $destroy event to see if
# the attribute has changed, if so we queue up the change.
scope.$on '$destroy', (value) ->
# No update
return if scope.object()[scope.attr] is scope.savedValue
currentValue = scope.object()[scope.attr] || ""
# For some reason the code attribute is removed from the object when cleared, so we add
# an emptyvalue so it gets updated properly
if scope.attr is "code" and scope.object()[scope.attr] is undefined
scope.object()["code"] = ""
# No update
return if currentValue is scope.savedValue
# Queuing up change
addPendingChange(scope.attr, scope.object()[scope.attr])
addPendingChange(scope.attr, currentValue)
# private

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
class WebhookEndpointFormComponent < ViewComponent::Base
def initialize(webhooks:, webhook_type:)
@webhooks = webhooks
@webhook_type = webhook_type
end
private
attr_reader :webhooks, :webhook_type
def is_webhook_payment_status?
webhook_type == "payment_status_changed"
end
end

View File

@@ -0,0 +1,27 @@
-# Create new endpoints
- if webhooks.empty? # Only one allowed for now.
%tr
%td= t("components.webhook_endpoint_form.event_types.#{webhook_type}")
%td
= form_with(url: helpers.account_webhook_endpoints_path, id: "#{webhook_type}_webhook_endpoint") do |f|
= f.url_field :'webhook_endpoint[url]', id: "#{webhook_type}_webhook_endpoint_url", placeholder: t('components.webhook_endpoint_form.url.create_placeholder'), required: true, size: 64
= f.hidden_field :'webhook_endpoint[webhook_type]', id: "#{webhook_type}_webhook_endpoint_webhook_type", value: webhook_type
%td.actions
= button_tag t(:create), class: 'button primary tiny no-margin', form: "#{webhook_type}_webhook_endpoint"
-# Existing endpoints
- webhooks.each do |webhook_endpoint|
%tr
%td= t("components.webhook_endpoint_form.event_types.#{webhook_type}")
%td= webhook_endpoint.url
%td.actions.endpoints-actions
- if webhook_endpoint.persisted?
= button_to helpers.account_webhook_endpoint_path(webhook_endpoint), method: :delete,
class: "tiny alert no-margin",
data: { confirm: I18n.t(:are_you_sure) } do
= I18n.t(:delete)
- if is_webhook_payment_status?
= form_tag helpers.webhook_endpoint_test_account_path(webhook_endpoint), class: "button_to", 'data-turbo': true do
= button_tag type: "submit", class: "tiny alert no-margin", data: { confirm: I18n.t(:are_you_sure) } do
= I18n.t("components.webhook_endpoint_form.test_endpoint")

View File

@@ -162,6 +162,18 @@ module Admin
end
end
def destroy
if @object.destroy
flash.now[:success] = flash_message_for(@object, :successfully_removed)
else
flash.now[:error] = @object.errors.full_messages.to_sentence
end
respond_to do |format|
format.turbo_stream { render :destroy, status: :ok }
end
end
protected
def delete_custom_tab

View File

@@ -61,7 +61,7 @@ module Admin
def destroy
if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
flash[:success] = Spree.t(:successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
format.js { render partial: "spree/admin/shared/destroy" }
@@ -76,7 +76,7 @@ module Admin
protected
def resource_not_found
flash[:error] = flash_message_for(model_class.new, :not_found)
flash[:error] = Spree.t(:not_found)
redirect_to collection_url
end

View File

@@ -30,7 +30,7 @@ module Admin
status = :ok
if @rule.destroy
flash[:success] = Spree.t(:successfully_removed, resource: "Tag Rule")
flash[:success] = Spree.t(:successfully_removed, resource: Spree.t(:tag_rule))
else
flash.now[:error] = t(".destroy_error")
status = :internal_server_error

View File

@@ -14,7 +14,7 @@ module Admin
)
if @voucher.save
flash[:success] = I18n.t(:successfully_created, resource: "Voucher")
flash[:success] = I18n.t(:successfully_created, resource: Spree.t(:voucher))
redirect_to edit_admin_enterprise_path(@enterprise, anchor: :vouchers_panel)
else
render_error

View File

@@ -68,7 +68,7 @@ module Spree
destroy_before
if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
flash[:success] = Spree.t(:successfully_removed)
end
redirect_to location_after_save

View File

@@ -16,7 +16,7 @@ module Spree
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
flash[:success] = Spree.t(:successfully_removed)
end
# if destroy fails it won't show any errors to the user
redirect_to spree.admin_product_product_properties_url(params[:product_id], @url_filters)

View File

@@ -36,7 +36,7 @@ module Spree
end
@object.touch :deleted_at
flash[:success] = flash_message_for(@object, :successfully_removed)
flash[:success] = Spree.t(:successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }

View File

@@ -5,7 +5,7 @@ module Spree
class TaxCategoriesController < ::Admin::ResourceController
def destroy
if @object.destroy
flash[:success] = flash_message_for(@object, :successfully_removed)
flash[:success] = Spree.t(:successfully_removed)
respond_with(@object) do |format|
format.html { redirect_to collection_url }
format.js { render partial: "spree/admin/shared/destroy" }

View File

@@ -1,7 +1,7 @@
# frozen_string_literal: true
class WebhookEndpointsController < BaseController
before_action :load_resource, only: :destroy
before_action :load_resource, only: [:destroy, :test]
def create
webhook_endpoint = spree_current_user.webhook_endpoints.new(webhook_endpoint_params)
@@ -25,12 +25,30 @@ class WebhookEndpointsController < BaseController
redirect_to redirect_path
end
def test
at = Time.zone.now
test_payload = Payments::WebhookPayload.test_data.to_hash
WebhookDeliveryJob.perform_later(@webhook_endpoint.url, "payment.completed", test_payload, at:)
flash[:success] = t(".success")
respond_with do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.update(
:flashes, partial: "shared/flashes", locals: { flashes: flash }
)
end
end
end
private
def load_resource
@webhook_endpoint = spree_current_user.webhook_endpoints.find(params[:id])
end
def webhook_endpoint_params
params.require(:webhook_endpoint).permit(:url)
params.require(:webhook_endpoint).permit(:url, :webhook_type)
end
def redirect_path

View File

@@ -63,8 +63,11 @@ module EnterprisesHelper
url = object_url(enterprise)
name = t(:delete)
options = {}
options[:class] = "delete-resource"
options[:data] = { action: 'remove', confirm: enterprise_confirm_delete_message(enterprise) }
options[:data] = {
turbo: true,
'turbo-method': 'delete',
'turbo-confirm': enterprise_confirm_delete_message(enterprise)
}
link_to_with_icon 'icon-trash', name, url, options
end

View File

@@ -50,11 +50,11 @@ class Enterprise < ApplicationRecord
has_many :distributed_orders, class_name: 'Spree::Order',
foreign_key: 'distributor_id',
inverse_of: :distributor,
dependent: :restrict_with_exception
dependent: :restrict_with_error
belongs_to :address, class_name: 'Spree::Address'
belongs_to :business_address, optional: true, class_name: 'Spree::Address', dependent: :destroy
has_many :enterprise_fees, dependent: :restrict_with_exception
has_many :enterprise_fees, dependent: :restrict_with_error
has_many :enterprise_roles, dependent: :destroy
has_many :users, through: :enterprise_roles
belongs_to :owner, class_name: 'Spree::User',
@@ -62,18 +62,18 @@ class Enterprise < ApplicationRecord
has_many :distributor_payment_methods,
inverse_of: :distributor,
foreign_key: :distributor_id,
dependent: :restrict_with_exception
dependent: :restrict_with_error
has_many :distributor_shipping_methods,
inverse_of: :distributor,
foreign_key: :distributor_id,
dependent: :restrict_with_exception
dependent: :restrict_with_error
has_many :payment_methods, through: :distributor_payment_methods
has_many :shipping_methods, through: :distributor_shipping_methods
has_many :customers, dependent: :destroy
has_many :inventory_items, dependent: :destroy
has_many :tag_rules, dependent: :destroy
has_one :stripe_account, dependent: :destroy
has_many :vouchers, dependent: :restrict_with_exception
has_many :vouchers, dependent: :restrict_with_error
has_many :connected_apps, dependent: :destroy
has_many :dfc_permissions, dependent: :destroy
has_one :custom_tab, dependent: :destroy
@@ -111,14 +111,14 @@ class Enterprise < ApplicationRecord
end
validates :logo,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ::Spree::Image::ACCEPTED_CONTENT_TYPES
validates :promo_image,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ::Spree::Image::ACCEPTED_CONTENT_TYPES
validates :white_label_logo,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ::Spree::Image::ACCEPTED_CONTENT_TYPES
validates :terms_and_conditions, content_type: {
in: "application/pdf",
message: I18n.t(:enterprise_terms_and_conditions_type_error),

View File

@@ -29,11 +29,11 @@ class EnterpriseGroup < ApplicationRecord
has_one_attached :promo_image, service: image_service
validates :logo,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ::Spree::Image::ACCEPTED_CONTENT_TYPES
validates :promo_image,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ::Spree::Image::ACCEPTED_CONTENT_TYPES
scope :by_position, -> { order('position ASC') }
scope :on_front_page, -> { where(on_front_page: true) }

View File

@@ -2,6 +2,8 @@
module Spree
class Image < Asset
ACCEPTED_CONTENT_TYPES = %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
has_one_attached :attachment, service: image_service do |attachment|
attachment.variant :mini, resize_to_fill: [48, 48]
attachment.variant :small, resize_to_fill: [227, 227]
@@ -11,8 +13,8 @@ module Spree
validates :attachment,
attached: true,
processable_image: true,
content_type: %r{\Aimage/(png|jpeg|gif|jpg|svg\+xml|webp)\Z}
processable_file: true,
content_type: ACCEPTED_CONTENT_TYPES
validate :no_attachment_errors
def self.default_image_url(size)

View File

@@ -101,6 +101,24 @@ module Spree
end
after_transition to: :completed, do: :set_captured_at
after_transition do |payment, transition|
# Catch any exceptions to prevent any rollback potentially
# preventing payment from going through
ActiveSupport::Notifications.instrument(
"ofn.payment_transition", payment: payment, event: transition.to
)
rescue StandardError => e
Rails.logger.fatal "ActiveSupport::Notification.instrument failed params: " \
"<event_type:ofn.payment_transition> " \
"<payment_id:#{payment.id}> " \
"<event:#{transition.to}>"
Alert.raise(
e,
metadata: {
event_tye: "ofn.payment_transition", payment_id: payment.id, event: transition.to
}
)
end
end
def money

View File

@@ -2,5 +2,11 @@
# Records a webhook url to send notifications to
class WebhookEndpoint < ApplicationRecord
WEBHOOK_TYPES = %w(order_cycle_opened payment_status_changed).freeze
validates :url, presence: true
validates :webhook_type, presence: true, inclusion: { in: WEBHOOK_TYPES }
scope :order_cycle_opened, -> { where(webhook_type: "order_cycle_opened") }
scope :payment_status, -> { where(webhook_type: "payment_status_changed") }
end

View File

@@ -11,7 +11,9 @@ class ImageImporter
image = Spree::Image.create do |img|
PrivateAddressCheck.only_public_connections do
img.attachment.attach(io: valid_url.open, filename:, metadata:)
io = valid_url.open
content_type = Marcel::MimeType.for(io)
img.attachment.attach(io:, filename:, metadata:, content_type:)
end
end
product.image = image if image

View File

@@ -11,10 +11,12 @@ module OrderCycles
.merge(coordinator_name: order_cycle.coordinator.name)
# Endpoints for coordinator owner
webhook_endpoints = order_cycle.coordinator.owner.webhook_endpoints
webhook_endpoints = order_cycle.coordinator.owner.webhook_endpoints.order_cycle_opened
# Plus unique endpoints for distributor owners (ignore duplicates)
webhook_endpoints |= order_cycle.distributors.map(&:owner).flat_map(&:webhook_endpoints)
webhook_endpoints |= order_cycle.distributors.map(&:owner).flat_map { |owner|
owner.webhook_endpoints.order_cycle_opened
}
webhook_endpoints.each do |endpoint|
WebhookDeliveryJob.perform_later(endpoint.url, event, webhook_payload, at:)

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
# Called by "ActiveSupport::Notifications" when an "ofn.payment_transition" occurs
# Event originate from Spree::Payment event machine
#
module Payments
class StatusChangedListenerService
def call(_name, started, _finished, _unique_id, payload)
event = "payment.#{payload[:event]}"
Payments::WebhookService.create_webhook_job(payment: payload[:payment], event:, at: started)
end
end
end

View File

@@ -0,0 +1,84 @@
# frozen_string_literal: true
module Payments
class WebhookPayload
def initialize(payment:, order:, enterprise:)
@payment = payment
@order = order
@enterprise = enterprise
end
def to_hash
{
payment: @payment.slice(:updated_at, :amount, :state),
enterprise: @enterprise.slice(:abn, :acn, :name)
.merge(address: @enterprise.address.slice(:address1, :address2, :city, :zipcode)),
order: @order.slice(:total, :currency).merge(line_items: line_items)
}.with_indifferent_access
end
def self.test_data
new(payment: test_payment, order: test_order, enterprise: test_enterprise)
end
def self.test_payment
{
updated_at: Time.zone.now,
amount: 0.00,
state: "completed"
}
end
def self.test_order
order = Spree::Order.new(
total: 0.00,
currency: "AUD",
)
tax_category = Spree::TaxCategory.new(name: "VAT")
product = Spree::Product.new(name: "Test product")
Spree::Variant.new(product:, display_name: "")
order.line_items << Spree::LineItem.new(
quantity: 1,
price: 20.00,
tax_category:,
product:,
unit_presentation: "1kg"
)
order
end
def self.test_enterprise
enterprise = Enterprise.new(
abn: "65797115831",
acn: "",
name: "TEST Enterprise",
)
enterprise.address = Spree::Address.new(
address1: "1 testing street",
address2: "",
city: "TestCity",
zipcode: "1234"
)
enterprise
end
private_class_method :test_payment, :test_order, :test_enterprise
private
def line_items
@order.line_items.map do |li|
li.slice(:quantity, :price)
.merge(
tax_category_name: li.tax_category&.name,
product_name: li.product.name,
name_to_display: li.display_name,
unit_to_display: li.unit_presentation
)
end
end
end
end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
# Create a webhook payload for a payment status event.
# The payload will be delivered asynchronously.
module Payments
class WebhookService
def self.create_webhook_job(payment:, event:, at:)
order = payment.order
payload = WebhookPayload.new(payment:, order:, enterprise: order.distributor).to_hash
coordinator = payment.order.order_cycle.coordinator
webhook_urls(coordinator).each do |url|
WebhookDeliveryJob.perform_later(url, event, payload, at:)
end
end
def self.webhook_urls(coordinator)
# url for coordinator owner
webhook_urls = coordinator.owner.webhook_endpoints.payment_status.pluck(:url)
# plus url for coordinator manager (ignore duplicate)
users_webhook_urls = coordinator.users.flat_map do |user|
user.webhook_endpoints.payment_status.pluck(:url)
end
webhook_urls | users_webhook_urls
end
end
end

View File

@@ -22,7 +22,7 @@
%tbody
= f.fields_for :collection do |enterprise_form|
- enterprise = enterprise_form.object
%tr{class: "enterprise-#{enterprise.id}"}
%tr{class: "enterprise-#{enterprise.id}", id: "resource-#{enterprise.id}"}
%td= link_to enterprise.name, main_app.edit_admin_enterprise_path(enterprise)
%td
= enterprise_form.check_box :is_primary_producer

View File

@@ -0,0 +1,4 @@
- unless flash[:error]
= turbo_stream.remove "resource-#{@object.id}"
= turbo_stream.append "flashes" do
= render(partial: 'admin/shared/flashes', locals: { flashes: flash })

View File

@@ -18,7 +18,7 @@
%span{ "ofn-with-tip": '{{ orderCycle.producerNames }}', "ng-show": 'orderCycle.producers.length > 3' }
{{ orderCycle.producers.length }}
= t('.suppliers')
%span{ "ng-hide": 'orderCycle.producers.length > 3', "ng-bind": 'orderCycle.producerNames' }
%span{ "ng-hide": 'orderCycle.producers.length > 3', "ng-bind-html": 'orderCycle.producerNames' }
%td.coordinator{ "ng-show": 'columns.coordinator.visible', "ng-bind-html": 'orderCycle.coordinator.name' }
%td.shops{ "ng-show": 'columns.shops.visible' }
%span{ "ofn-with-tip": '{{ orderCycle.shopNames }}', "ng-show": 'orderCycle.shops.length > 3' }

View File

@@ -10,24 +10,5 @@
%th= t('.url.header')
%th.actions
%tbody
-# Existing endpoints
- @user.webhook_endpoints.each do |webhook_endpoint|
%tr
%td= t('.event_types.order_cycle_opened') # For now, we only support one type.
%td= webhook_endpoint.url
%td.actions
- if webhook_endpoint.persisted?
= button_to account_webhook_endpoint_path(webhook_endpoint), method: :delete,
class: "tiny alert no-margin",
data: { confirm: I18n.t(:are_you_sure)} do
= I18n.t(:delete)
-# Create new
- if @user.webhook_endpoints.empty? # Only one allowed for now.
%tr
%td= t('.event_types.order_cycle_opened') # For now, we only support one type.
%td
= form_for(@user.webhook_endpoints.build, url: account_webhook_endpoints_path, id: 'new_webhook_endpoint') do |f|
= f.url_field :url, placeholder: t('.url.create_placeholder'), required: true, size: 64
%td.actions
= button_tag t(:create), class: 'button primary tiny no-margin', form: 'new_webhook_endpoint'
= render WebhookEndpointFormComponent.new(webhooks: @user.webhook_endpoints.order_cycle_opened, webhook_type: "order_cycle_opened")
= render WebhookEndpointFormComponent.new(webhooks: @user.webhook_endpoints.payment_status, webhook_type: "payment_status_changed")

View File

@@ -9,7 +9,8 @@
}
}
.saved_cards, .no_cards {
.saved_cards,
.no_cards {
margin-bottom: 1em;
}
@@ -26,7 +27,7 @@
}
}
.authorised_shops{
.authorised_shops {
table {
width: 100%;
}
@@ -39,7 +40,9 @@
a {
color: $clr-brick;
&:hover, &:active, &:focus {
&:hover,
&:active,
&:focus {
color: $clr-brick-med-bright;
}
}
@@ -60,7 +63,8 @@
}
}
i.ofn-i_059-producer, i.ofn-i_060-producer-reversed {
i.ofn-i_059-producer,
i.ofn-i_060-producer-reversed {
font-size: 3rem;
display: inline-block;
margin-right: 0.25rem;
@@ -92,7 +96,8 @@
visibility: hidden;
}
.transaction-group {}
.transaction-group {
}
table {
border-radius: $radius-medium $radius-medium 0 0;
@@ -161,6 +166,15 @@ table {
//
// Unfortunately we can't use Scss's interpolation
// https://sass-lang.com/documentation/interpolation. We're using a too old version perhaps?
right: calc(12px + 2*2px + 2*1px);
right: calc(12px + 2 * 2px + 2 * 1px);
}
}
// Webhook Endpoints
td.endpoints-actions {
display: flex;
form {
padding-right: $padding-small;
}
}

View File

@@ -58,10 +58,15 @@ module Openfoodnetwork
Spree::Core::Engine.routes.default_url_options[:host] = ENV["SITE_URL"] if Rails.env == 'test'
end
# We reload the routes here
# so that the appended/prepended routes are available to the application.
config.after_initialize do
# We reload the routes here
# so that the appended/prepended routes are available to the application.
Rails.application.routes_reloader.reload!
# Subscribe to payment transition events
ActiveSupport::Notifications.subscribe(
"ofn.payment_transition", Payments::StatusChangedListenerService.new
)
end
initializer "spree.environment", before: :load_config_initializers do |app|

View File

@@ -11,7 +11,7 @@
# `button_to` view helper will render `<button>` element, regardless of whether
# or not the content is passed as the first argument or as a block.
# Rails.application.config.action_view.button_to_generates_button_tag = true
Rails.application.config.action_view.button_to_generates_button_tag = true
# `stylesheet_link_tag` view helper will not render the media attribute by default.
Rails.application.config.action_view.apply_stylesheet_media_default = false

View File

@@ -93,7 +93,7 @@
# serializer. Therefore, this setting should only be enabled after all replicas
# have been successfully upgraded to Rails 7.1.
#++
# Rails.application.config.active_job.use_big_decimal_serializer = true
Rails.application.config.active_job.use_big_decimal_serializer = true
###
# Specify if an `ArgumentError` should be raised if `Rails.cache` `fetch` or

View File

@@ -138,26 +138,66 @@ en:
# Used by active_storage_validations
errors:
messages:
content_type_invalid: "has an invalid content type"
file_size_out_of_range: "size %{file_size} is not between required range"
limit_out_of_range: "total number is out of range"
image_metadata_missing: "is not a valid image"
dimension_min_inclusion: "must be greater than or equal to %{width} x %{height} pixel."
dimension_max_inclusion: "must be less than or equal to %{width} x %{height} pixel."
dimension_width_inclusion: "width is not included between %{min} and %{max} pixel."
dimension_height_inclusion: "height is not included between %{min} and %{max} pixel."
dimension_width_greater_than_or_equal_to: "width must be greater than or equal to %{length} pixel."
dimension_height_greater_than_or_equal_to: "height must be greater than or equal to %{length} pixel."
dimension_width_less_than_or_equal_to: "width must be less than or equal to %{length} pixel."
dimension_height_less_than_or_equal_to: "height must be less than or equal to %{length} pixel."
dimension_width_equal_to: "width must be equal to %{length} pixel."
dimension_height_equal_to: "height must be equal to %{length} pixel."
aspect_ratio_not_square: "must be a square image"
aspect_ratio_not_portrait: "must be a portrait image"
aspect_ratio_not_landscape: "must be a landscape image"
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
aspect_ratio_unknown: "has an unknown aspect ratio"
image_not_processable: "is not a valid image"
content_type_invalid:
one: "has an invalid content type (authorized content type is %{authorized_human_content_types})"
other: "has an invalid content type (authorized content types are %{authorized_human_content_types})"
content_type_spoofed:
one: "has a content type that is not equivalent to the one that is detected through its content (authorized content type is %{authorized_human_content_types})"
other: "has a content type that is not equivalent to the one that is detected through its content (authorized content types are %{authorized_human_content_types})"
file_size_not_less_than: "file size must be less than %{max} (current size is %{file_size})"
file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max} (current size is %{file_size})"
file_size_not_greater_than: "file size must be greater than %{min} (current size is %{file_size})"
file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min} (current size is %{file_size})"
file_size_not_between: "file size must be between %{min} and %{max} (current size is %{file_size})"
file_size_not_equal_to: "file size must be equal to %{exact} (current size is %{file_size})"
total_file_size_not_less_than: "total file size must be less than %{max} (current size is %{total_file_size})"
total_file_size_not_less_than_or_equal_to: "total file size must be less than or equal to %{max} (current size is %{total_file_size})"
total_file_size_not_greater_than: "total file size must be greater than %{min} (current size is %{total_file_size})"
total_file_size_not_greater_than_or_equal_to: "total file size must be greater than or equal to %{min} (current size is %{total_file_size})"
total_file_size_not_between: "total file size must be between %{min} and %{max} (current size is %{total_file_size})"
total_file_size_not_equal_to: "total file size must be equal to %{exact} (current size is %{total_file_size})"
duration_not_less_than: "duration must be less than %{max} (current duration is %{duration})"
duration_not_less_than_or_equal_to: "duration must be less than or equal to %{max} (current duration is %{duration})"
duration_not_greater_than: "duration must be greater than %{min} (current duration is %{duration})"
duration_not_greater_than_or_equal_to: "duration must be greater than or equal to %{min} (current duration is %{duration})"
duration_not_between: "duration must be between %{min} and %{max} (current duration is %{duration})"
duration_not_equal_to: "duration must be equal to %{exact} (current duration is %{duration})"
limit_out_of_range:
zero: "no files attached (must have between %{min} and %{max} files)"
one: "only 1 file attached (must have between %{min} and %{max} files)"
other: "total number of files must be between %{min} and %{max} files (there are %{count} files attached)"
limit_min_not_reached:
zero: "no files attached (must have at least %{min} files)"
one: "only 1 file attached (must have at least %{min} files)"
other: "%{count} files attached (must have at least %{min} files)"
limit_max_exceeded:
zero: "no files attached (maximum is %{max} files)"
one: "too many files attached (maximum is %{max} files, got %{count})"
other: "too many files attached (maximum is %{max} files, got %{count})"
attachment_missing: "is missing its attachment"
media_metadata_missing: "is not a valid media file"
dimension_min_not_included_in: "must be greater than or equal to %{width} x %{height} pixels"
dimension_max_not_included_in: "must be less than or equal to %{width} x %{height} pixels"
dimension_width_not_included_in: "width is not included between %{min} and %{max} pixels"
dimension_height_not_included_in: "height is not included between %{min} and %{max} pixels"
dimension_width_not_greater_than_or_equal_to: "width must be greater than or equal to %{length} pixels"
dimension_height_not_greater_than_or_equal_to: "height must be greater than or equal to %{length} pixels"
dimension_width_not_less_than_or_equal_to: "width must be less than or equal to %{length} pixels"
dimension_height_not_less_than_or_equal_to: "height must be less than or equal to %{length} pixels"
dimension_width_not_equal_to: "width must be equal to %{length} pixels"
dimension_height_not_equal_to: "height must be equal to %{length} pixels"
aspect_ratio_not_square: "must be square (current file is %{width}x%{height}px)"
aspect_ratio_not_portrait: "must be portrait (current file is %{width}x%{height}px)"
aspect_ratio_not_landscape: "must be landscape (current file is %{width}x%{height}px)"
aspect_ratio_not_x_y: "must be %{authorized_aspect_ratios} (current file is %{width}x%{height}px)"
aspect_ratio_invalid: "has an invalid aspect ratio (valid aspect ratios are %{authorized_aspect_ratios})"
file_not_processable: "is not identified as a valid media file"
pages_not_less_than: "page count must be less than %{max} (current page count is %{pages})"
pages_not_less_than_or_equal_to: "page count must be less than or equal to %{max} (current page count is %{pages})"
pages_not_greater_than: "page count must be greater than %{min} (current page count is %{pages})"
pages_not_greater_than_or_equal_to: "page count must be greater than or equal to %{min} (current page count is %{pages})"
pages_not_between: "page count must be between %{min} and %{max} (current page count is %{pages})"
pages_not_equal_to: "page count must be equal to %{exact} (current page count is %{pages})"
not_found:
title: "The page you were looking for doesn't exist (404)"
message_html: "<b>Please try again</b>
@@ -4088,6 +4128,8 @@ en:
destroy:
success: Webhook endpoint successfully deleted
error: Webhook endpoint failed to delete
test:
success: Some test data will be sent to the webhook url
spree:
order_updated: "Order Updated"
@@ -4195,6 +4237,8 @@ en:
logourl: "Logourl"
are_you_sure_delete: "Are you sure you want to delete this record?"
confirm_delete: "Confirm Deletion"
tag_rule: "Tag Rule"
voucher: "Voucher"
configurations: "Configurations"
general_settings: "General Settings"
@@ -4938,13 +4982,10 @@ en:
webhook_endpoints:
title: Webhook Endpoints
description: Events in the system may trigger webhooks to external systems.
event_types:
order_cycle_opened: Order Cycle Opened
event_type:
header: Event type
url:
header: Endpoint URL
create_placeholder: Enter the URL of the remote webhook endpoint
developer_settings:
title: Developer Settings
form:
@@ -5093,7 +5134,13 @@ en:
add_tag_rule_modal:
select_rule_type: "Select a rule type:"
add_rule: "Add Rule"
webhook_endpoint_form:
url:
create_placeholder: Enter the URL of the remote webhook endpoint
event_types:
order_cycle_opened: Order Cycle Opened
payment_status_changed: Post webhook on Payment status change
test_endpoint: Test webhook endpoint
# Gem to prevent bot form submissions
invisible_captcha:

View File

@@ -34,6 +34,7 @@ Spree::Core::Engine.routes.draw do
resource :account, :controller => 'users' do
resources :webhook_endpoints, only: [:create, :destroy], controller: '/webhook_endpoints'
post '/webhook_endpoints/:id/test', to: "/webhook_endpoints#test", as: "webhook_endpoint_test"
end
match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddTypeToWebhookEndpoints < ActiveRecord::Migration[7.1]
def up
# Using "order_cycle_opened" as default will update existing record
change_table(:webhook_endpoints, bulk: true) do |t|
t.column :webhook_type, :string, limit: 255, null: false, default: "order_cycle_opened"
end
# Drop the default value
change_column_default :webhook_endpoints, :webhook_type, nil
end
def down
remove_column :webhook_endpoints, :webhook_type
end
end

View File

@@ -1143,6 +1143,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_11_26_005628) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "user_id", default: 0, null: false
t.string "webhook_type", limit: 255, null: false
t.index ["user_id"], name: "index_webhook_endpoints_on_user_id"
end

View File

@@ -13,14 +13,7 @@ module DfcProvider
# It means that our permissions to access data on another platform changed.
# We will need to pull the updated data.
def create
unless current_user.is_a? ApiUser
unauthorized "You need to authenticate as authorised platform (client_id)."
return
end
unless current_user.id == "lf-dev"
unauthorized "Your client_id is not authorised on this platform."
return
end
return if rendered_errors?
event = JSON.parse(request.body.read)
enterprises_url = event["enterpriseUrlid"]
@@ -45,8 +38,23 @@ module DfcProvider
private
def unauthorized(message)
render_message(:unauthorized, message)
def rendered_errors?
unless current_user.is_a? ApiUser
render_message(
:unauthorized,
"You need to authenticate as authorised platform (client_id).",
)
return true
end
unless current_user.id == "lf-dev"
render_message(
:unauthorized,
"Your client_id is not authorised on this platform.",
)
return true
end
false
end
def render_message(status, message)

View File

@@ -19,12 +19,14 @@ class ImageBuilder < DfcBuilder
def self.import(image_link)
url = URI.parse(image_link)
filename = File.basename(image_link)
filename = File.basename(url.path)
metadata = { custom: { origin: image_link } }
Spree::Image.new.tap do |image|
PrivateAddressCheck.only_public_connections do
image.attachment.attach(io: url.open, filename:, metadata:)
io = url.open
content_type = Marcel::MimeType.for(io)
image.attachment.attach(io:, filename:, metadata:, content_type:)
end
end
rescue StandardError

View File

@@ -251,7 +251,7 @@ RSpec.describe SuppliedProductImporter do
supplied_product.isVariantOf << tomatoes
imported_product = importer.import_variant(supplied_product, supplier).product
expect(imported_product.image.attachment.filename).to eq "tomato.png?v=1"
expect(imported_product.image.attachment.filename).to eq "tomato.png"
expect {
importer.import_variant(supplied_product, supplier).product
@@ -266,7 +266,7 @@ RSpec.describe SuppliedProductImporter do
}
.to change { imported_product.image }
expect(imported_product.image.attachment.filename).to eq "tomato.png?v=2"
expect(imported_product.image.attachment.filename).to eq "tomato.png"
end
context "when spree_product_uri doesn't match the server host" do

View File

@@ -45,7 +45,8 @@ RSpec.describe Api::V0::ProductImagesController do
expect(response).to have_http_status :unprocessable_entity
expect(product_without_image.image).to be_nil
expect(json_response["id"]).to eq nil
expect(json_response["errors"]).to include "Attachment has an invalid content type"
expect(json_response["errors"]).to include "Attachment is " \
"not identified as a valid media file"
end
end
end

View File

@@ -11,14 +11,16 @@ RSpec.describe WebhookEndpointsController do
describe "#create" do
it "creates a webhook_endpoint" do
expect {
spree_post :create, { url: "https://url" }
spree_post :create, { url: "https://url", webhook_type: "order_cycle_opened" }
}.to change {
user.webhook_endpoints.count
}.by(1)
expect(flash[:success]).to be_present
expect(flash[:error]).to be_blank
expect(user.webhook_endpoints.first.url).to eq "https://url"
webhook = user.webhook_endpoints.first
expect(webhook.url).to eq "https://url"
expect(webhook.webhook_type).to eq "order_cycle_opened"
end
it "shows error if parameters not specified" do
@@ -33,17 +35,20 @@ RSpec.describe WebhookEndpointsController do
end
it "redirects back to referrer" do
spree_post :create, { url: "https://url" }
spree_post :create, { url: "https://url", webhook_type: "order_cycle_opened" }
expect(response).to redirect_to "/account#/developer_settings"
end
end
describe "#destroy" do
let!(:webhook_endpoint) { user.webhook_endpoints.create(url: "https://url") }
let!(:webhook_endpoint) {
user.webhook_endpoints.create(url: "https://url", webhook_type: "order_cycle_opened")
}
it "destroys a webhook_endpoint" do
webhook_endpoint2 = user.webhook_endpoints.create!(url: "https://url2")
webhook_endpoint2 = user.webhook_endpoints.create!(url: "https://url2",
webhook_type: "order_cycle_opened")
expect {
spree_delete :destroy, { id: webhook_endpoint.id }
@@ -64,4 +69,22 @@ RSpec.describe WebhookEndpointsController do
expect(response).to redirect_to "/account#/developer_settings"
end
end
describe "#test" do
let(:webhook_endpoint) {
user.webhook_endpoints.create(url: "https://url", webhook_type: "payment_status_changed" )
}
subject { spree_post :test, id: webhook_endpoint.id, format: :turbo_stream }
it "enqueus a webhook job" do
expect { subject }.to enqueue_job(WebhookDeliveryJob).exactly(1).times
end
it "shows a success mesage" do
subject
expect(flash[:success]).to eq "Some test data will be sent to the webhook url"
end
end
end

BIN
spec/fixtures/files/logo.bmp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -58,37 +58,40 @@ RSpec.describe Enterprise do
expect(EnterpriseRelationship.where(id: [er1, er2])).to be_empty
end
it "raises a DeleteRestrictionError on destroy if distributed_orders exist" do
it "does not destroy distributed_orders upon destroy" do
enterprise = create(:distributor_enterprise)
create_list(:order, 2, distributor: enterprise)
expect do
enterprise.destroy
end.to raise_error(ActiveRecord::DeleteRestrictionError,
/Cannot delete record because of dependent distributed_orders/)
.and change { Spree::Order.count }.by(0)
expect(enterprise.errors.full_messages).to eq(
["Cannot delete record because dependent distributed orders exist"]
)
end.to change { Spree::Order.count }.by(0)
end
it "raises an DeleteRestrictionError on destroy if distributor_payment_methods exist" do
it "does not destroy distributor_payment_methods upon destroy" do
enterprise = create(:distributor_enterprise)
create_list(:distributor_payment_method, 2, distributor: enterprise)
expect do
enterprise.destroy
end.to raise_error(ActiveRecord::DeleteRestrictionError,
/Cannot delete record because of dependent distributor_payment_methods/)
.and change { DistributorPaymentMethod.count }.by(0)
expect(enterprise.errors.full_messages).to eq(
["Cannot delete record because dependent distributor payment methods exist"]
)
end.to change { Spree::Order.count }.by(0)
end
it "raises an DeleteRestrictionError on destroy if distributor_shipping_methods exist" do
it "does not destroy distributor_shipping_methods upon destroy" do
enterprise = create(:distributor_enterprise)
create_list(:distributor_shipping_method, 2, distributor: enterprise)
expect do
enterprise.destroy
end.to raise_error(ActiveRecord::DeleteRestrictionError,
/Cannot delete record because of dependent distributor_shipping_methods/)
.and change { DistributorShippingMethod.count }.by(0)
expect(enterprise.errors.full_messages).to eq(
["Cannot delete record because dependent distributor shipping methods exist"]
)
end.to change { Spree::Order.count }.by(0)
end
it "does not destroy enterprise_fees upon destroy" do
@@ -97,9 +100,10 @@ RSpec.describe Enterprise do
expect do
enterprise.destroy
end.to raise_error(ActiveRecord::DeleteRestrictionError,
/Cannot delete record because of dependent enterprise_fees/)
.and change { EnterpriseFee.count }.by(0)
expect(enterprise.errors.full_messages).to eq(
["Cannot delete record because dependent enterprise fees exist"]
)
end.to change { Spree::Order.count }.by(0)
end
it "does not destroy vouchers upon destroy" do
@@ -110,9 +114,10 @@ RSpec.describe Enterprise do
expect do
enterprise.destroy
end.to raise_error(ActiveRecord::DeleteRestrictionError,
/Cannot delete record because of dependent vouchers/)
.and change { Voucher.count }.by(0)
expect(enterprise.errors.full_messages).to eq(
["Cannot delete record because dependent vouchers exist"]
)
end.to change { Spree::Order.count }.by(0)
end
describe "relationships to other enterprises" do
@@ -391,24 +396,8 @@ RSpec.describe Enterprise do
let(:content_type) { 'image/png' }
before do
blob = instance_double(
"ActiveStorage::Blob",
filename: ActiveStorage::Filename.new('white-label-logo.png'),
content_type:,
byte_size: 1024
)
# InstanceDouble is not working for attachment case as the blob method is not yet defined
# on instantiation.
attachment = double(
"ActiveStorage::Attached::One",
blank?: false,
attached?: true,
blob:
)
allow(enterprise)
.to receive(:white_label_logo).and_return(attachment)
blob = Rack::Test::UploadedFile.new('spec/fixtures/files/logo.png', content_type)
enterprise.white_label_logo.attach(blob)
end
context 'when the file attached is a PNG image' do
@@ -419,6 +408,12 @@ RSpec.describe Enterprise do
context 'when the file attached is a BMP image' do
let(:content_type) { 'image/bmp' }
before do
blob = Rack::Test::UploadedFile.new('spec/fixtures/files/logo.bmp', content_type)
enterprise.white_label_logo.attach(blob)
end
it 'is not valid' do
expect(enterprise).not_to be_valid
end

View File

@@ -35,6 +35,12 @@ module Spree
}
before do
# mock the call with "ofn.payment_transition" so we don't call the related listener
# and services
allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
allow(ActiveSupport::Notifications).to receive(:instrument)
.with("ofn.payment_transition", any_args).and_return(nil)
allow(order).to receive_message_chain(:line_items, :empty?).and_return(false)
allow(order).to receive_messages total: 100
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").

View File

@@ -3,6 +3,13 @@
require 'spec_helper'
RSpec.describe Spree::Payment do
before do
# mock the call with "ofn.payment_transition" so we don't call the related listener and services
allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
allow(ActiveSupport::Notifications).to receive(:instrument)
.with("ofn.payment_transition", any_args).and_return(nil)
end
context 'original specs from Spree' do
before { Stripe.api_key = "sk_test_12345" }
let(:order) { create(:order) }
@@ -1064,4 +1071,17 @@ RSpec.describe Spree::Payment do
expect(payment.captured_at).to be_present
end
end
describe "payment transition" do
it "notifies of payment status change" do
payment = create(:payment)
allow(ActiveSupport::Notifications).to receive(:instrument).and_call_original
expect(ActiveSupport::Notifications).to receive(:instrument).with(
"ofn.payment_transition", payment: payment, event: "processing"
)
payment.started_processing!
end
end
end

View File

@@ -5,5 +5,9 @@ require 'spec_helper'
RSpec.describe WebhookEndpoint do
describe "validations" do
it { is_expected.to validate_presence_of(:url) }
it {
is_expected.to validate_inclusion_of(:webhook_type)
.in_array(%w(order_cycle_opened payment_status_changed))
}
end
end

View File

@@ -7,7 +7,7 @@ RSpec.describe ImageImporter do
let(:product) { create(:product) }
describe "#import" do
it "downloads from the Internet", :vcr do
it "downloads from the Internet", :vcr, :aggregate_failures do
expect {
subject.import(ofn_url, product)
}.to change {

View File

@@ -22,7 +22,7 @@ RSpec.describe OrderCycles::WebhookService do
# The co-ordinating enterprise has a non-owner user with an endpoint.
# They shouldn't receive a notification.
coordinator_user = create(:user, enterprises: [coordinator])
coordinator_user.webhook_endpoints.create!(url: "http://coordinator_user_url")
coordinator_user.webhook_endpoints.order_cycle_opened.create!(url: "http://coordinator_user_url")
expect{ subject }
.not_to enqueue_job(WebhookDeliveryJob).with("http://coordinator_user_url", any_args)
@@ -30,7 +30,7 @@ RSpec.describe OrderCycles::WebhookService do
context "coordinator owner has endpoint configured" do
before do
coordinator.owner.webhook_endpoints.create! url: "http://coordinator_owner_url"
coordinator.owner.webhook_endpoints.order_cycle_opened.create!(url: "http://coordinator_owner_url")
end
it "creates webhook payload for order cycle coordinator" do
@@ -77,7 +77,7 @@ RSpec.describe OrderCycles::WebhookService do
let(:two_distributors) {
(1..2).map do |i|
user = create(:user)
user.webhook_endpoints.create!(url: "http://distributor#{i}_owner_url")
user.webhook_endpoints.order_cycle_opened.create!(url: "http://distributor#{i}_owner_url")
create(:distributor_enterprise, owner: user)
end
}
@@ -109,7 +109,7 @@ RSpec.describe OrderCycles::WebhookService do
}
it "creates only one webhook payload for the user's endpoint" do
user.webhook_endpoints.create! url: "http://coordinator_owner_url"
user.webhook_endpoints.order_cycle_opened.create!(url: "http://coordinator_owner_url")
expect{ subject }
.to enqueue_job(WebhookDeliveryJob).with("http://coordinator_owner_url", any_args)
@@ -128,7 +128,7 @@ RSpec.describe OrderCycles::WebhookService do
}
let(:supplier) {
user = create(:user)
user.webhook_endpoints.create!(url: "http://supplier_owner_url")
user.webhook_endpoints.order_cycle_opened.create!(url: "http://supplier_owner_url")
create(:supplier_enterprise, owner: user)
}

View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Payments::StatusChangedListenerService do
let(:name) { "ofn.payment_transition" }
let(:started) { Time.zone.parse("2025-11-28 09:00:00") }
let(:finished) { Time.zone.parse("2025-11-28 09:00:02") }
let(:unique_id) { "d3a7ac9f635755fcff2c" }
let(:payload) { { payment:, event: "completed" } }
let(:payment) { build(:payment) }
subject { described_class.new }
describe "#call" do
it "calls Payments::WebhookService" do
expect(Payments::WebhookService).to receive(:create_webhook_job).with(
payment:, event: "payment.completed", at: started
)
subject.call(name, started, finished, unique_id, payload)
end
end
end

View File

@@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Payments::WebhookPayload do
describe "#to_hash" do
let(:order) { create(:completed_order_with_totals, order_cycle: ) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:payment) { create(:payment, :completed, amount: order.total, order:) }
let(:tax_category) { create(:tax_category) }
subject { described_class.new(payment:, order:, enterprise: order.distributor) }
it "returns a hash with the relevant data" do
order.line_items.update_all(tax_category_id: tax_category.id)
enterprise = order.distributor
line_items = order.line_items.map do |li|
{
quantity: li.quantity,
price: li.price,
tax_category_name: li.tax_category&.name,
product_name: li.product.name,
name_to_display: li.display_name,
unit_to_display: li.unit_presentation
}
end
payload = {
payment: {
updated_at: payment.updated_at,
amount: payment.amount,
state: payment.state
},
enterprise: {
abn: enterprise.abn,
acn: enterprise.acn,
name: enterprise.name,
address: {
address1: enterprise.address.address1,
address2: enterprise.address.address2,
city: enterprise.address.city,
zipcode: enterprise.address.zipcode
}
},
order: {
total: order.total,
currency: order.currency,
line_items: line_items
}
}.with_indifferent_access
expect(subject.to_hash).to eq(payload)
end
end
describe ".test_data" do
it "returns a hash with test data" do
test_payload = {
payment: {
updated_at: kind_of(Time),
amount: 0.00,
state: "completed"
},
enterprise: {
abn: "65797115831",
acn: "",
name: "TEST Enterprise",
address: {
address1: "1 testing street",
address2: "",
city: "TestCity",
zipcode: "1234"
}
},
order: {
total: 0.00,
currency: "AUD",
line_items: [
{
quantity: 1,
price: 20.00.to_d,
tax_category_name: "VAT",
product_name: "Test product",
name_to_display: nil,
unit_to_display: "1kg"
}
]
}
}.with_indifferent_access
expect(described_class.test_data.to_hash).to match(test_payload)
end
end
end

View File

@@ -0,0 +1,116 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Payments::WebhookService do
let(:order) { create(:completed_order_with_totals, order_cycle: ) }
let(:order_cycle) { create(:simple_order_cycle) }
let(:payment) { create(:payment, :completed, amount: order.total, order:) }
let(:tax_category) { create(:tax_category) }
let(:at) { Time.zone.parse("2025-11-26 09:00:02") }
subject { described_class.create_webhook_job(payment: payment, event: "payment.completed", at:) }
describe "creating payloads" do
context "with order cycle coordinator owner webhook endpoints configured" do
before do
order.order_cycle.coordinator.owner.webhook_endpoints.payment_status.create!(
url: "http://coordinator.payment.url"
)
end
it "calls endpoint for the owner if the order cycle coordinator" do
expect{ subject }
.to enqueue_job(WebhookDeliveryJob).exactly(1).times
.with("http://coordinator.payment.url", "payment.completed", any_args)
end
it "creates webhook payload with payment details" do
order.line_items.update_all(tax_category_id: tax_category.id)
enterprise = order.distributor
line_items = order.line_items.map do |li|
{
quantity: li.quantity,
price: li.price,
tax_category_name: li.tax_category&.name,
product_name: li.product.name,
name_to_display: li.display_name,
unit_to_display: li.unit_presentation
}
end
data = {
payment: {
updated_at: payment.updated_at,
amount: payment.amount,
state: payment.state
},
enterprise: {
abn: enterprise.abn,
acn: enterprise.acn,
name: enterprise.name,
address: {
address1: enterprise.address.address1,
address2: enterprise.address.address2,
city: enterprise.address.city,
zipcode: enterprise.address.zipcode
}
},
order: {
total: order.total,
currency: order.currency,
line_items: line_items
}
}
expect{ subject }
.to enqueue_job(WebhookDeliveryJob).exactly(1).times
.with("http://coordinator.payment.url", "payment.completed", hash_including(data), at:)
end
context "with coordinator manager with webhook endpoint configured" do
let(:user1) { create(:user) }
let(:user2) { create(:user) }
before do
coordinator = order.order_cycle.coordinator
coordinator.users << user1
coordinator.users << user2
end
it "calls endpoint for all user managing the order cycle coordinator" do
user1.webhook_endpoints.payment_status.create!(url: "http://user1.payment.url")
user2.webhook_endpoints.payment_status.create!(url: "http://user2.payment.url")
expect{ subject }
.to enqueue_job(WebhookDeliveryJob)
.with("http://coordinator.payment.url", "payment.completed", any_args)
.and enqueue_job(WebhookDeliveryJob)
.with("http://user1.payment.url", "payment.completed", any_args)
.and enqueue_job(WebhookDeliveryJob)
.with("http://user2.payment.url", "payment.completed", any_args)
end
context "wiht duplicate webhook endpoints configured" do
it "calls each unique configured endpoint" do
user1.webhook_endpoints.payment_status.create!(url: "http://coordinator.payment.url")
user2.webhook_endpoints.payment_status.create!(url: "http://user2.payment.url")
expect{ subject }
.to enqueue_job(WebhookDeliveryJob)
.with("http://coordinator.payment.url", "payment.completed", any_args)
.and enqueue_job(WebhookDeliveryJob)
.with("http://user2.payment.url", "payment.completed", any_args)
end
end
end
end
context "with no webhook configured" do
it "does not call endpoint" do
expect{ subject }.not_to enqueue_job(WebhookDeliveryJob)
end
end
end
end

View File

@@ -55,4 +55,15 @@ RSpec.describe "Tax Categories" do
expect(page).to have_content("desc 99")
end
end
context "admin deleting a tax category" do
it "should be able to delete an existing tax category" do
create(:tax_category, name: "To be removed")
click_link "Tax Categories"
accept_confirm('Are you sure?') do
within_row(1) { find(".icon-trash").click }
end
expect(page).not_to have_content("To be removed")
end
end
end

View File

@@ -111,6 +111,7 @@ RSpec.describe 'Customers' do
end
end
expect(page).not_to have_selector "tr#c_#{customer2.id}"
expect(page).not_to have_content 'You have unsaved changes'
}.to change{ Customer.count }.by(-1)
end

View File

@@ -68,6 +68,48 @@ RSpec.describe '
expect(page).to have_checked_field "enterprise_visible_only_through_links"
end
it "deleting an existing enterprise successfully" do
enterprise = create(:enterprise)
user = create(:user)
admin = login_as_admin
visit '/admin/enterprises'
expect do
accept_alert do
within "tr.enterprise-#{enterprise.id}" do
first("a", text: 'Delete').click
end
end
expect(page).to have_content("Successfully Removed")
end.to change{ Enterprise.count }.by(-1)
end
it "deleting an existing enterprise unsuccessfully" do
enterprise = create(:enterprise)
create(:order, distributor: enterprise)
user = create(:user)
admin = login_as_admin
visit '/admin/enterprises'
expect do
accept_alert do
within "tr.enterprise-#{enterprise.id}" do
first("a", text: 'Delete').click
end
end
expect(page).to have_content("Cannot delete record because dependent distributed order")
expect(page).to have_content(enterprise.name)
end.to change{ Enterprise.count }.by(0)
end
it "editing an existing enterprise" do
@enterprise = create(:enterprise)
e2 = create(:enterprise)

View File

@@ -604,7 +604,7 @@ RSpec.describe '
click_button "Create"
expect(page).to have_text "Attachment has an invalid content type"
expect(page).to have_text "Attachment is not a valid image"
expect(page).to have_text "Attachment is not identified as a valid media file"
end
it "deleting product images" do

View File

@@ -770,12 +770,12 @@ RSpec.describe 'As an enterprise user, I can update my products' do
end
end
it 'shows a modal telling not a valid image when uploading a non valid image file' do
it 'shows a modal telling not a valid image when uploading an invalid image file' do
within ".reveal-modal" do
attach_file 'image[attachment]',
Rails.public_path.join('invalid_image.jpg'),
visible: false
expect(page).to have_content /Attachment is not a valid image/
expect(page).to have_content /Attachment is not identified as a valid media file/
end
end
end

View File

@@ -39,15 +39,33 @@ RSpec.describe "Developer Settings" do
describe "Webhook Endpoints" do
it "creates a new webhook endpoint and deletes it" do
within "#webhook_endpoints" do
fill_in "webhook_endpoint_url", with: "https://url"
within(:table_row, ["Order Cycle Opened"]) do
fill_in "order_cycle_opened_webhook_endpoint_url", with: "https://url"
click_button I18n.t(:create)
expect(page.document).to have_content I18n.t('webhook_endpoints.create.success')
expect(page).to have_content "https://url"
click_button "Create"
expect(page.document).to have_content "Webhook endpoint successfully created"
expect(page).to have_content "https://url"
click_button I18n.t(:delete)
expect(page.document).to have_content I18n.t('webhook_endpoints.destroy.success')
accept_confirm do
click_button "Delete"
end
end
expect(page.document).to have_content "Webhook endpoint successfully deleted"
expect(page).not_to have_content "https://url"
within(:table_row, ["Post webhook on Payment status change"]) do
fill_in "payment_status_changed_webhook_endpoint_url", with: "https://url/payment"
click_button "Create"
expect(page.document).to have_content "Webhook endpoint successfully created"
expect(page).to have_content "https://url/payment"
accept_confirm do
click_button "Delete"
end
end
expect(page.document).to have_content "Webhook endpoint successfully deleted"
expect(page).not_to have_content "https://url/payment"
end
end
end

View File

@@ -9621,9 +9621,9 @@ tr46@^5.1.0:
punycode "^2.3.1"
trix@*:
version "2.1.15"
resolved "https://registry.yarnpkg.com/trix/-/trix-2.1.15.tgz#fabad796ea779a8ae96522402fbc214cbfc4015f"
integrity sha512-LoaXWczdTUV8+3Box92B9b1iaDVbxD14dYemZRxi3PwY+AuDm97BUJV2aHLBUFPuDABhxp0wzcbf0CxHCVmXiw==
version "2.1.16"
resolved "https://registry.yarnpkg.com/trix/-/trix-2.1.16.tgz#601be839258b87cc83019915650c50eb7cbc161e"
integrity sha512-XtZgWI+oBvLzX7CWnkIf+ZWC+chL+YG/TkY43iMTV0Zl+CJjn18B1GJUCEWJ8qgfpcyMBuysnNAfPWiv2sV14A==
dependencies:
dompurify "^3.2.5"