mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge pull request #13777 from rioug/13481-webhook-payment
Payment status change webhook
This commit is contained in:
16
app/components/webhook_endpoint_form_component.rb
Normal file
16
app/components/webhook_endpoint_form_component.rb
Normal 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
|
||||||
@@ -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")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class WebhookEndpointsController < BaseController
|
class WebhookEndpointsController < BaseController
|
||||||
before_action :load_resource, only: :destroy
|
before_action :load_resource, only: [:destroy, :test]
|
||||||
|
|
||||||
def create
|
def create
|
||||||
webhook_endpoint = spree_current_user.webhook_endpoints.new(webhook_endpoint_params)
|
webhook_endpoint = spree_current_user.webhook_endpoints.new(webhook_endpoint_params)
|
||||||
@@ -25,12 +25,30 @@ class WebhookEndpointsController < BaseController
|
|||||||
redirect_to redirect_path
|
redirect_to redirect_path
|
||||||
end
|
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
|
def load_resource
|
||||||
@webhook_endpoint = spree_current_user.webhook_endpoints.find(params[:id])
|
@webhook_endpoint = spree_current_user.webhook_endpoints.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def webhook_endpoint_params
|
def webhook_endpoint_params
|
||||||
params.require(:webhook_endpoint).permit(:url)
|
params.require(:webhook_endpoint).permit(:url, :webhook_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirect_path
|
def redirect_path
|
||||||
|
|||||||
@@ -101,6 +101,24 @@ module Spree
|
|||||||
end
|
end
|
||||||
|
|
||||||
after_transition to: :completed, do: :set_captured_at
|
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
|
end
|
||||||
|
|
||||||
def money
|
def money
|
||||||
|
|||||||
@@ -2,5 +2,11 @@
|
|||||||
|
|
||||||
# Records a webhook url to send notifications to
|
# Records a webhook url to send notifications to
|
||||||
class WebhookEndpoint < ApplicationRecord
|
class WebhookEndpoint < ApplicationRecord
|
||||||
|
WEBHOOK_TYPES = %w(order_cycle_opened payment_status_changed).freeze
|
||||||
|
|
||||||
validates :url, presence: true
|
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
|
end
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ module OrderCycles
|
|||||||
.merge(coordinator_name: order_cycle.coordinator.name)
|
.merge(coordinator_name: order_cycle.coordinator.name)
|
||||||
|
|
||||||
# Endpoints for coordinator owner
|
# 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)
|
# 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|
|
webhook_endpoints.each do |endpoint|
|
||||||
WebhookDeliveryJob.perform_later(endpoint.url, event, webhook_payload, at:)
|
WebhookDeliveryJob.perform_later(endpoint.url, event, webhook_payload, at:)
|
||||||
|
|||||||
13
app/services/payments/status_changed_listener_service.rb
Normal file
13
app/services/payments/status_changed_listener_service.rb
Normal 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
|
||||||
84
app/services/payments/webhook_payload.rb
Normal file
84
app/services/payments/webhook_payload.rb
Normal 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
|
||||||
30
app/services/payments/webhook_service.rb
Normal file
30
app/services/payments/webhook_service.rb
Normal 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
|
||||||
@@ -10,24 +10,5 @@
|
|||||||
%th= t('.url.header')
|
%th= t('.url.header')
|
||||||
%th.actions
|
%th.actions
|
||||||
%tbody
|
%tbody
|
||||||
-# Existing endpoints
|
= render WebhookEndpointFormComponent.new(webhooks: @user.webhook_endpoints.order_cycle_opened, webhook_type: "order_cycle_opened")
|
||||||
- @user.webhook_endpoints.each do |webhook_endpoint|
|
= render WebhookEndpointFormComponent.new(webhooks: @user.webhook_endpoints.payment_status, webhook_type: "payment_status_changed")
|
||||||
%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'
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.saved_cards, .no_cards {
|
.saved_cards,
|
||||||
|
.no_cards {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.authorised_shops{
|
.authorised_shops {
|
||||||
table {
|
table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@@ -39,7 +40,9 @@
|
|||||||
a {
|
a {
|
||||||
color: $clr-brick;
|
color: $clr-brick;
|
||||||
|
|
||||||
&:hover, &:active, &:focus {
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
color: $clr-brick-med-bright;
|
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;
|
font-size: 3rem;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
@@ -92,7 +96,8 @@
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transaction-group {}
|
.transaction-group {
|
||||||
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
border-radius: $radius-medium $radius-medium 0 0;
|
border-radius: $radius-medium $radius-medium 0 0;
|
||||||
@@ -161,6 +166,15 @@ table {
|
|||||||
//
|
//
|
||||||
// Unfortunately we can't use Scss's interpolation
|
// Unfortunately we can't use Scss's interpolation
|
||||||
// https://sass-lang.com/documentation/interpolation. We're using a too old version perhaps?
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,15 @@ module Openfoodnetwork
|
|||||||
Spree::Core::Engine.routes.default_url_options[:host] = ENV["SITE_URL"] if Rails.env == 'test'
|
Spree::Core::Engine.routes.default_url_options[:host] = ENV["SITE_URL"] if Rails.env == 'test'
|
||||||
end
|
end
|
||||||
|
|
||||||
# We reload the routes here
|
|
||||||
# so that the appended/prepended routes are available to the application.
|
|
||||||
config.after_initialize do
|
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!
|
Rails.application.routes_reloader.reload!
|
||||||
|
|
||||||
|
# Subscribe to payment transition events
|
||||||
|
ActiveSupport::Notifications.subscribe(
|
||||||
|
"ofn.payment_transition", Payments::StatusChangedListenerService.new
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
initializer "spree.environment", before: :load_config_initializers do |app|
|
initializer "spree.environment", before: :load_config_initializers do |app|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@
|
|||||||
# serializer. Therefore, this setting should only be enabled after all replicas
|
# serializer. Therefore, this setting should only be enabled after all replicas
|
||||||
# have been successfully upgraded to Rails 7.1.
|
# 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
|
# Specify if an `ArgumentError` should be raised if `Rails.cache` `fetch` or
|
||||||
|
|||||||
@@ -4088,6 +4088,8 @@ en:
|
|||||||
destroy:
|
destroy:
|
||||||
success: Webhook endpoint successfully deleted
|
success: Webhook endpoint successfully deleted
|
||||||
error: Webhook endpoint failed to delete
|
error: Webhook endpoint failed to delete
|
||||||
|
test:
|
||||||
|
success: Some test data will be sent to the webhook url
|
||||||
|
|
||||||
spree:
|
spree:
|
||||||
order_updated: "Order Updated"
|
order_updated: "Order Updated"
|
||||||
@@ -4940,13 +4942,10 @@ en:
|
|||||||
webhook_endpoints:
|
webhook_endpoints:
|
||||||
title: Webhook Endpoints
|
title: Webhook Endpoints
|
||||||
description: Events in the system may trigger webhooks to external systems.
|
description: Events in the system may trigger webhooks to external systems.
|
||||||
event_types:
|
|
||||||
order_cycle_opened: Order Cycle Opened
|
|
||||||
event_type:
|
event_type:
|
||||||
header: Event type
|
header: Event type
|
||||||
url:
|
url:
|
||||||
header: Endpoint URL
|
header: Endpoint URL
|
||||||
create_placeholder: Enter the URL of the remote webhook endpoint
|
|
||||||
developer_settings:
|
developer_settings:
|
||||||
title: Developer Settings
|
title: Developer Settings
|
||||||
form:
|
form:
|
||||||
@@ -5095,7 +5094,13 @@ en:
|
|||||||
add_tag_rule_modal:
|
add_tag_rule_modal:
|
||||||
select_rule_type: "Select a rule type:"
|
select_rule_type: "Select a rule type:"
|
||||||
add_rule: "Add Rule"
|
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
|
# Gem to prevent bot form submissions
|
||||||
invisible_captcha:
|
invisible_captcha:
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ Spree::Core::Engine.routes.draw do
|
|||||||
|
|
||||||
resource :account, :controller => 'users' do
|
resource :account, :controller => 'users' do
|
||||||
resources :webhook_endpoints, only: [:create, :destroy], controller: '/webhook_endpoints'
|
resources :webhook_endpoints, only: [:create, :destroy], controller: '/webhook_endpoints'
|
||||||
|
post '/webhook_endpoints/:id/test', to: "/webhook_endpoints#test", as: "webhook_endpoint_test"
|
||||||
end
|
end
|
||||||
|
|
||||||
match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get
|
match '/admin/orders/bulk_management' => 'admin/orders#bulk_management', :as => "admin_bulk_order_management", via: :get
|
||||||
|
|||||||
16
db/migrate/20251124043324_add_type_to_webhook_endpoints.rb
Normal file
16
db/migrate/20251124043324_add_type_to_webhook_endpoints.rb
Normal 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
|
||||||
@@ -1143,6 +1143,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_11_26_005628) do
|
|||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.bigint "user_id", default: 0, 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"
|
t.index ["user_id"], name: "index_webhook_endpoints_on_user_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -11,14 +11,16 @@ RSpec.describe WebhookEndpointsController do
|
|||||||
describe "#create" do
|
describe "#create" do
|
||||||
it "creates a webhook_endpoint" do
|
it "creates a webhook_endpoint" do
|
||||||
expect {
|
expect {
|
||||||
spree_post :create, { url: "https://url" }
|
spree_post :create, { url: "https://url", webhook_type: "order_cycle_opened" }
|
||||||
}.to change {
|
}.to change {
|
||||||
user.webhook_endpoints.count
|
user.webhook_endpoints.count
|
||||||
}.by(1)
|
}.by(1)
|
||||||
|
|
||||||
expect(flash[:success]).to be_present
|
expect(flash[:success]).to be_present
|
||||||
expect(flash[:error]).to be_blank
|
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
|
end
|
||||||
|
|
||||||
it "shows error if parameters not specified" do
|
it "shows error if parameters not specified" do
|
||||||
@@ -33,17 +35,20 @@ RSpec.describe WebhookEndpointsController do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "redirects back to referrer" do
|
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"
|
expect(response).to redirect_to "/account#/developer_settings"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#destroy" do
|
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
|
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 {
|
expect {
|
||||||
spree_delete :destroy, { id: webhook_endpoint.id }
|
spree_delete :destroy, { id: webhook_endpoint.id }
|
||||||
@@ -64,4 +69,22 @@ RSpec.describe WebhookEndpointsController do
|
|||||||
expect(response).to redirect_to "/account#/developer_settings"
|
expect(response).to redirect_to "/account#/developer_settings"
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ module Spree
|
|||||||
}
|
}
|
||||||
|
|
||||||
before 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)
|
||||||
|
|
||||||
allow(order).to receive_message_chain(:line_items, :empty?).and_return(false)
|
allow(order).to receive_message_chain(:line_items, :empty?).and_return(false)
|
||||||
allow(order).to receive_messages total: 100
|
allow(order).to receive_messages total: 100
|
||||||
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").
|
stub_request(:get, "https://api.stripe.com/v1/payment_intents/12345").
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
require 'spec_helper'
|
require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Spree::Payment do
|
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
|
context 'original specs from Spree' do
|
||||||
before { Stripe.api_key = "sk_test_12345" }
|
before { Stripe.api_key = "sk_test_12345" }
|
||||||
let(:order) { create(:order) }
|
let(:order) { create(:order) }
|
||||||
@@ -1064,4 +1071,17 @@ RSpec.describe Spree::Payment do
|
|||||||
expect(payment.captured_at).to be_present
|
expect(payment.captured_at).to be_present
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -5,5 +5,9 @@ require 'spec_helper'
|
|||||||
RSpec.describe WebhookEndpoint do
|
RSpec.describe WebhookEndpoint do
|
||||||
describe "validations" do
|
describe "validations" do
|
||||||
it { is_expected.to validate_presence_of(:url) }
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ RSpec.describe OrderCycles::WebhookService do
|
|||||||
# The co-ordinating enterprise has a non-owner user with an endpoint.
|
# The co-ordinating enterprise has a non-owner user with an endpoint.
|
||||||
# They shouldn't receive a notification.
|
# They shouldn't receive a notification.
|
||||||
coordinator_user = create(:user, enterprises: [coordinator])
|
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 }
|
expect{ subject }
|
||||||
.not_to enqueue_job(WebhookDeliveryJob).with("http://coordinator_user_url", any_args)
|
.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
|
context "coordinator owner has endpoint configured" do
|
||||||
before 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
|
end
|
||||||
|
|
||||||
it "creates webhook payload for order cycle coordinator" do
|
it "creates webhook payload for order cycle coordinator" do
|
||||||
@@ -77,7 +77,7 @@ RSpec.describe OrderCycles::WebhookService do
|
|||||||
let(:two_distributors) {
|
let(:two_distributors) {
|
||||||
(1..2).map do |i|
|
(1..2).map do |i|
|
||||||
user = create(:user)
|
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)
|
create(:distributor_enterprise, owner: user)
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ RSpec.describe OrderCycles::WebhookService do
|
|||||||
}
|
}
|
||||||
|
|
||||||
it "creates only one webhook payload for the user's endpoint" 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 }
|
expect{ subject }
|
||||||
.to enqueue_job(WebhookDeliveryJob).with("http://coordinator_owner_url", any_args)
|
.to enqueue_job(WebhookDeliveryJob).with("http://coordinator_owner_url", any_args)
|
||||||
@@ -128,7 +128,7 @@ RSpec.describe OrderCycles::WebhookService do
|
|||||||
}
|
}
|
||||||
let(:supplier) {
|
let(:supplier) {
|
||||||
user = create(:user)
|
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)
|
create(:supplier_enterprise, owner: user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
95
spec/services/payments/webhook_payload_spec.rb
Normal file
95
spec/services/payments/webhook_payload_spec.rb
Normal 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
|
||||||
116
spec/services/payments/webhook_service_spec.rb
Normal file
116
spec/services/payments/webhook_service_spec.rb
Normal 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
|
||||||
@@ -39,15 +39,33 @@ RSpec.describe "Developer Settings" do
|
|||||||
describe "Webhook Endpoints" do
|
describe "Webhook Endpoints" do
|
||||||
it "creates a new webhook endpoint and deletes it" do
|
it "creates a new webhook endpoint and deletes it" do
|
||||||
within "#webhook_endpoints" 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)
|
click_button "Create"
|
||||||
expect(page.document).to have_content I18n.t('webhook_endpoints.create.success')
|
expect(page.document).to have_content "Webhook endpoint successfully created"
|
||||||
expect(page).to have_content "https://url"
|
expect(page).to have_content "https://url"
|
||||||
|
|
||||||
click_button I18n.t(:delete)
|
accept_confirm do
|
||||||
expect(page.document).to have_content I18n.t('webhook_endpoints.destroy.success')
|
click_button "Delete"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
expect(page.document).to have_content "Webhook endpoint successfully deleted"
|
||||||
expect(page).not_to have_content "https://url"
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user