payload.rb 3.6 KB

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