From d7505bcef43880f5f678bd3357038348dd1d3a10 Mon Sep 17 00:00:00 2001 From: Gaetan Craig-Riou Date: Wed, 3 Dec 2025 17:06:45 +1100 Subject: [PATCH] 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 --- .../webhook_endpoints_controller.rb | 35 +------ app/services/payments/webhook_payload.rb | 84 ++++++++++++++++ app/services/payments/webhook_service.rb | 21 +--- .../services/payments/webhook_payload_spec.rb | 95 +++++++++++++++++++ 4 files changed, 183 insertions(+), 52 deletions(-) create mode 100644 app/services/payments/webhook_payload.rb create mode 100644 spec/services/payments/webhook_payload_spec.rb diff --git a/app/controllers/webhook_endpoints_controller.rb b/app/controllers/webhook_endpoints_controller.rb index 730362df57..f78c101484 100644 --- a/app/controllers/webhook_endpoints_controller.rb +++ b/app/controllers/webhook_endpoints_controller.rb @@ -25,40 +25,9 @@ class WebhookEndpointsController < BaseController redirect_to redirect_path end - def test # rubocop:disable Metrics/MethodLength + def test at = Time.zone.now - test_payload = { - payment: { - updated_at: at, - 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, - tax_category_name: "VAT", - product_name: "Test product", - name_to_display: "", - unit_to_display: "1kg" - } - ] - } - } + test_payload = Payments::WebhookPayload.test_data.to_hash WebhookDeliveryJob.perform_later(@webhook_endpoint.url, "payment.completed", test_payload, at:) diff --git a/app/services/payments/webhook_payload.rb b/app/services/payments/webhook_payload.rb new file mode 100644 index 0000000000..92745fd1ff --- /dev/null +++ b/app/services/payments/webhook_payload.rb @@ -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 diff --git a/app/services/payments/webhook_service.rb b/app/services/payments/webhook_service.rb index 451a073e3c..427811d3b6 100644 --- a/app/services/payments/webhook_service.rb +++ b/app/services/payments/webhook_service.rb @@ -7,26 +7,9 @@ module Payments class WebhookService def self.create_webhook_job(payment:, event:, at:) order = payment.order - enterprise = order.distributor + payload = WebhookPayload.new(payment:, order:, enterprise: order.distributor).to_hash - 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 - - payload = { - 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) - } - - coordinator = order.order_cycle.coordinator + coordinator = payment.order.order_cycle.coordinator webhook_urls(coordinator).each do |url| WebhookDeliveryJob.perform_later(url, event, payload, at:) end diff --git a/spec/services/payments/webhook_payload_spec.rb b/spec/services/payments/webhook_payload_spec.rb new file mode 100644 index 0000000000..936fb49a86 --- /dev/null +++ b/spec/services/payments/webhook_payload_spec.rb @@ -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 formated hash" 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