payload.rb 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. module Whatsapp::Webhook
  3. class Payload
  4. include Whatsapp::Webhook::Concerns::HasChannel
  5. def initialize(json:, uuid:, signature:)
  6. channel = find_channel!(uuid)
  7. secret = channel.options[:app_secret]
  8. digest = OpenSSL::Digest.new('sha256')
  9. raise ValidationError if OpenSSL::HMAC.hexdigest(digest, secret, json) != signature
  10. @channel = channel
  11. @data = JSON.parse(json).deep_symbolize_keys
  12. raise ProcessableError, __('Mismatching phone number id.') if !phone_number_id?
  13. end
  14. def process
  15. raise ProcessableError, __('API error.') if protocol_error?
  16. raise ProcessableError, __('Unsupported subscription type.') if !subscription_message?
  17. if status_message?
  18. process_status_message
  19. elsif message?
  20. process_message
  21. else
  22. # NeverShouldHappen(TM)
  23. raise ProcessableError, __('Unsupported webhook payload.')
  24. end
  25. end
  26. private
  27. def process_message
  28. raise ProcessableError if message_error?
  29. type = @data[:entry].first[:changes].first[:value][:messages].first[:type]
  30. klass = "Whatsapp::Webhook::Message::#{type.capitalize}"
  31. raise ProcessableError, __('Unsupported message type.') if Whatsapp::Webhook::Message.descendants.map(&:to_s).exclude?(klass)
  32. klass.constantize.new(data: @data, channel: @channel).process
  33. end
  34. def process_status_message
  35. status = @data[:entry].first[:changes].first[:value][:statuses].first[:status]
  36. klass = "Whatsapp::Webhook::Message::Status::#{status.capitalize}"
  37. log_status_message
  38. return if Whatsapp::Webhook::Message::Status.descendants.map(&:to_s).exclude?(klass)
  39. klass.constantize.new(data: @data, channel: @channel).process
  40. end
  41. def phone_number_id?
  42. @data[:entry].first[:changes].first[:value][:metadata][:phone_number_id] == @channel.options[:phone_number_id]
  43. end
  44. def subscription_message?
  45. @data[:entry].first[:changes].first[:field] == 'messages'
  46. end
  47. def protocol_error?
  48. @data[:entry].first[:changes].first[:value].key?(:error)
  49. end
  50. def message_error?
  51. @data[:entry].first[:changes].first[:value][:messages].first.key?(:errors)
  52. end
  53. def message?
  54. @data[:entry].first[:changes].first[:value].key?(:messages)
  55. end
  56. def status_message?
  57. @data[:entry].first[:changes].first[:value].key?(:statuses)
  58. end
  59. def failed_status_message?
  60. @data[:entry].first[:changes].first[:value][:statuses].first[:status] == 'failed'
  61. end
  62. def log_status_message
  63. HttpLog.create(
  64. direction: 'in',
  65. facility: 'WhatsApp::Business',
  66. url: "#{Setting.get('http_type')}://#{Setting.get('fqdn')}/#{Rails.configuration.api_path}/channels_whatsapp_webhook/#{@channel.options[:callback_url_uuid]}",
  67. ip: @channel.options[:phone_number],
  68. status: 200,
  69. request: { content: @data },
  70. response: { content: {} },
  71. method: 'POST',
  72. )
  73. end
  74. class ValidationError < StandardError
  75. def initialize
  76. super(__('The WhatsApp webhook payload could not be validated.'))
  77. end
  78. end
  79. class ProcessableError < StandardError
  80. attr_reader :reason
  81. def initialize(reason = nil)
  82. @reason = reason
  83. super(__('The WhatsApp webhook payload could not be processed.'))
  84. end
  85. end
  86. end
  87. end