Add payment confirmation notifications

This commit is contained in:
Hugo Peixoto 2023-08-07 13:50:50 +01:00
parent d3f6db5196
commit dccad36474
17 changed files with 304 additions and 15 deletions

View File

@ -33,7 +33,7 @@ class ContributionsController < ApplicationController
[year, @due_members.select {|m| m.expires_on < Date.new(year, 12, 31) }.map { |m| [year, @due_members.select {|m| m.expires_on < Date.new(year, 12, 31) }.map { |m|
OpenStruct.new( OpenStruct.new(
member: m, member: m,
amount: m.employed? ? 30 : 6, amount: m.employed? ? Config.regular_payment_value : Config.reduced_payment_value,
) )
}] }]
end end
@ -51,18 +51,15 @@ class ContributionsController < ApplicationController
# POST /contributions # POST /contributions
def create def create
@contribution = @member.contributions.build(contribution_params) success = @member.register_contribution(
contribution_params,
params.dig(:contribution, :overriden_expires_on),
params.dig(:contribution, :should_send_notification) == "1")
Contribution.transaction do if success
if @contribution.save redirect_to @member, notice: "Contribution was successfully created."
@member.handle_new_contribution(@contribution, params.dig(:contribution, :overriden_expires_on)) else
@member.reset_status! render :new, status: :unprocessable_entity
@member.regenerate_notifications
redirect_to @member, notice: "Contribution was successfully created."
else
render :new, status: :unprocessable_entity
end
end end
end end

View File

@ -29,6 +29,8 @@ class MembersController < ApplicationController
if @member.save if @member.save
@member.reset_status! @member.reset_status!
NotificationMailer.with(member: @member).registration.deliver_now!
redirect_to @member, notice: "Member was successfully created." redirect_to @member, notice: "Member was successfully created."
else else
render :new, status: :unprocessable_entity render :new, status: :unprocessable_entity

View File

@ -2,4 +2,43 @@ class PaymentsController < ApplicationController
def show def show
@payment = Payment.find(params[:id]) @payment = Payment.find(params[:id])
end end
def callback
if ENV["IFTHENPAY_AP_KEY"] != params["key"]
render status: 403, json: { error: "invalid anti phishing key" }
else
member = Member.find_by(number: params["id"].to_i)
payment = IfThenPay
.payments(params["payment_datetime"])
.select { |p| p["Id"] == params["id"] }
.first
if payment.nil?
render status: 400, json: { error: "couldn't find payment" }
else
# TODO: handle double payments
contribution_params = {
eurocents: payment["Valor"],
payment_method: {
"MB" => "multibanco",
"MBWAY" => "mbway"
}.fetch(params["payment_method"]),
payment_on: params["payment_datetime"],
payment_reference: payment["Terminal"],
}
success = member.register_contribution(
contribution_params,
nil,
true
)
if success
render json: { ok: "yes" }
else
render status: 500, json: { error: "error registering payment" }
end
end
end
end
end end

View File

@ -16,4 +16,11 @@ module IfThenPay
JSON.parse(response.body) JSON.parse(response.body)
end end
def self.payments(date)
URI("https://ifthenpay.com/ifmbws/ifmbws.asmx/getPaymentsJson?chavebackoffice=#{ENV['IFTHENPAY_BO_KEY']}&entidade=&subentidade=&dtHrInicio=#{date}&dtHrFim=#{date}&referencia=&valor=&sandbox=0")
.then{|u| Net::HTTP.get(u)}
.then{|b| Nokogiri::XML(b).child.child.text}
.then{|x| JSON.parse(x)}
end
end end

View File

@ -1,35 +1,55 @@
class NotificationMailer < ApplicationMailer class NotificationMailer < ApplicationMailer
before_action :set_notification
# Subject can be set in your I18n file at config/locales/en.yml # Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup: # with the following lookup:
# #
# en.notification_mailer.expiration_in_60d.subject # en.notification_mailer.expiration_in_60d.subject
# #
def expiration_in_60d def expiration_in_60d
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def expiration_in_30d def expiration_in_30d
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def expired def expired
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def expired_30d_ago def expired_30d_ago
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def expired_60d_ago def expired_60d_ago
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def cancelled def cancelled
set_notification
mail to: @notification.member.email mail to: @notification.member.email
end end
def registration
@member = params[:member]
@payment = @member.create_payment
mail to: @member.email
end
def first_payment_confirmation
@contribution = params[:contribution]
mail to: @contribution.member.email
end
def payment_renewal_confirmation
@contribution = params[:contribution]
mail to: @contribution.member.email
end
private private
def set_notification def set_notification
@notification = params[:notification] @notification = params[:notification]

View File

@ -56,6 +56,37 @@ class Member < ApplicationRecord
end end
end end
def register_contribution(contribution_params, overriden_expires_on, should_send_notification)
Contribution.transaction do
is_first_contribution = self.contributions.empty?
contribution = self.contributions.build(contribution_params)
if contribution.save
self.handle_new_contribution(contribution, overriden_expires_on)
self.reset_status!
self.regenerate_notifications
if should_send_notification
if is_first_contribution
NotificationMailer
.with(contribution: contribution)
.first_payment_confirmation
.deliver_now!
else
NotificationMailer
.with(contribution: contribution)
.payment_renewal_confirmation
.deliver_now!
end
end
true
else
false
end
end
end
def handle_new_contribution(contribution, overriden_expires_on) def handle_new_contribution(contribution, overriden_expires_on)
if joined_on.nil? if joined_on.nil?
self.joined_on = contribution.payment_on self.joined_on = contribution.payment_on

View File

@ -28,6 +28,10 @@
<td><label for="contribution_payment_reference"><%= t('contributions.attributes.payment_reference') %></label></td> <td><label for="contribution_payment_reference"><%= t('contributions.attributes.payment_reference') %></label></td>
<td><%= form.text_field :payment_reference %></td> <td><%= form.text_field :payment_reference %></td>
</tr> </tr>
<tr>
<td><label for="contribution_should_send_notification"><%= t('contributions.attributes.should_send_notification') %></label></td>
<td><%= form.check_box :should_send_notification, checked: true %></td>
</tr>
<tr> <tr>
<td colspan=2> <td colspan=2>
<%= t('contributions.new.expires_on_warning') %> <%= t('contributions.new.expires_on_warning') %>

View File

@ -0,0 +1,60 @@
<p><%= t('notification_mailer.greetings', display_name: @contribution.member.display_name) %></p>
<p>
Bem-vinde à ANSOL! Confirmamos que recebemos o teu pagamento, e tua inscrição
está válida até <strong><%= @contribution.member.expires_on %></strong>.
</p>
<p>Detalhes do pagamento:</p>
<ul>
<li><strong>Valor:</strong> <%= @contribution.eurocents %></li>
<li><strong>Método:</strong> <%= @contribution.payment_method %></li>
<li><strong>Data:</strong> <%= @contribution.payment_on %></li>
</ul>
<p>
Vamos inscrever-te na lista de discussão de sócios ansol-socios@ansol.org.
Quando o fizermos vais receber um email de confirmação. A maior parte de
comunicações que são enviadas são os relatórios trimestrais (disponíveis
também no nosso site em https://ansol.org/noticias/), convocatórias para as
assembleias gerais, e convites para participar na reunião mensal com a direcção.
</p>
<p>
Na primeira quarta feira de todos os meses às 22h a direcção faz uma reunião
aberta para que os sócios possam trazer assuntos / fazer perguntas / etc. As
reuniões são feitas no nosso Jitsi, em
<a href="https://jitsi.ansol.org/ansol-direccao">https://jitsi.ansol.org/ansol-direccao</a>.
</p>
<p>Temos uma sala da comunidade da ANSOL aberta ao público à qual podes aceder:</p>
<ul>
<li>Via Matrix: #geral:ansol.org, ou</li>
<li>Via Telegram: https://t.me/ansolgeral</li>
</ul>
<p>
Também temos uma sala de Matrix reservada a sócios (#socios:ansol.org).
Envia-nos o teu nome de utilizador de Matrix e nós adicionamos-te à sala.
Caso não conheças o Matrix, temos uma pequena introdução aqui:
<a href="https://gist.github.com/hugopeixoto/128a313ae33109f4b0a55f7242c660f0">
https://gist.github.com/hugopeixoto/128a313ae33109f4b0a55f7242c660f0
</a>
</p>
<p>Podes seguir as nossas actividades online através de várias plataformas:</p>
<ul>
<li><a href="https://floss.social/@ansol">Mastodon</a></li>
<li><a href="https://viste.pt/c/ansol/">Peertube</a></li>
<li><a href="https://git.ansol.org/ansol/">Git</a></li>
<li><a href="https://twitter.com/ANSOL">Twitter</a></li>
</ul>
<p>
Qualquer dúvida em relação às actividades actuais da ANSOL, não hesites em
mandar email.
</p>
<%= render partial: "cheers" %>

View File

@ -0,0 +1,42 @@
<%= t('notification_mailer.greetings', display_name: @contribution.member.display_name) %>
Bem-vinde à ANSOL! Confirmamos que recebemos o teu pagamento, e tua inscrição
está válida até <%= @contribution.member.expires_on %>.
Detalhes do pagamento:
* Valor: <%= @contribution.eurocents %>
* Método: <%= @contribution.payment_method %>
* Data: <%= @contribution.payment_on %>
Vamos inscrever-te na lista de discussão de sócios ansol-socios@ansol.org.
Quando o fizermos vais receber um email de confirmação. A maior parte de
comunicações que são enviadas são os relatórios trimestrais (disponíveis
também no nosso site em https://ansol.org/noticias/), convocatórias para as
assembleias gerais, e convites para participar na reunião mensal com a direcção.
Na primeira quarta feira de todos os meses às 22h a direcção faz uma reunião
aberta para que os sócios possam trazer assuntos / fazer perguntas / etc. As
reuniões são feitas no nosso Jitsi, em https://jitsi.ansol.org/ansol-direccao.
Temos uma sala da comunidade da ANSOL aberta ao público à qual podes aceder:
* Via Matrix: #geral:ansol.org, ou
* Via Telegram: https://t.me/ansolgeral
Também temos uma sala de Matrix reservada a sócios (#socios:ansol.org).
Envia-nos o teu nome de utilizador de Matrix e nós adicionamos-te à sala.
Caso não conheças o Matrix, temos uma pequena introdução aqui:
https://gist.github.com/hugopeixoto/128a313ae33109f4b0a55f7242c660f0
Podes seguir as nossas actividades online através de várias plataformas:
* Mastodon: https://floss.social/@ansol
* Peertube: https://viste.pt/c/ansol/
* Git: https://git.ansol.org/ansol/
* Twitter: https://twitter.com/ANSOL
Qualquer dúvida em relação às actividades actuais da ANSOL, não hesites em
mandar email.
<%= render partial: "cheers" %>

View File

@ -0,0 +1,16 @@
<p><%= t('notification_mailer.greetings', display_name: @contribution.member.display_name) %></p>
<p>
Recebemos o teu pagamento. Obrigado por continuares a apoiar a ANSOL. A tua
inscrição foi renovada e está válida até
<strong><%= @contribution.member.expires_on %></strong>.
</p>
<p>Detalhes do pagamento:</p>
<ul>
<li><strong>Valor:</strong> <%= @contribution.eurocents %></li>
<li><strong>Método:</strong> <%= @contribution.payment_method %></li>
<li><strong>Data:</strong> <%= @contribution.payment_on %></li>
</ul>
<%= render partial: "cheers" %>

View File

@ -0,0 +1,12 @@
<%= t('notification_mailer.greetings', display_name: @contribution.member.display_name) %>
Recebemos o teu pagamento. Obrigado por continuares a apoiar a ANSOL. A tua
inscrição foi renovada e está válida até <%= @contribution.member.expires_on %>.
Detalhes do pagamento:
* Valor: <%= @contribution.eurocents %>
* Método: <%= @contribution.payment_method %>
* Data: <%= @contribution.payment_on %>
<%= render partial: "cheers" %>

View File

@ -0,0 +1,22 @@
<p><%= t('notification_mailer.greetings', display_name: @member.display_name) %></p>
<p>
Obrigado pelo interesse em fazer parte da ANSOL. A tua candidatura foi
aprovada pela direcção.
</p>
<p>
Para finalizar o teu registo, falta apenas o pagamento da quota anual, no
valor de <%= number_with_precision(Config.regular_payment_value, precision: 2) %>€
(ou <%= number_with_precision(Config.reduced_payment_value, precision: 2) %>€
para pessoas reformadas, desempregadas, ou estudantes).
</p>
<%= render partial: "payment", locals: { payment: @payment } %>
<p>
Depois do pagamento estar confirmado, enviamos-te informações sobre os canais
de comunicação da associação, reuniões, etc.
</p>
<%= render partial: "cheers" %>

View File

@ -0,0 +1,16 @@
<%= t('notification_mailer.greetings', display_name: @member.display_name) %>
Obrigado pelo interesse em fazer parte da ANSOL. A tua candidatura foi aprovada
pela direcção.
Para finalizar o teu registo, falta apenas o pagamento da quota anual, no valor
de <%= number_with_precision(Config.regular_payment_value, precision: 2) %>€
(ou <%= number_with_precision(Config.reduced_payment_value, precision: 2) %>€
para pessoas reformadas, desempregadas, ou estudantes).
<%= render partial: "payment", locals: { payment: @payment } %>
Depois do pagamento estar confirmado, enviamos-te informações sobre os canais
de comunicação da associação, reuniões, etc.
<%= render partial: "cheers" %>

View File

@ -54,6 +54,8 @@ en:
student: "Student" student: "Student"
retired: "Retired" retired: "Retired"
contributions: contributions:
attributes:
should_send_notification: Send payment receipt
due: due:
title: "Due contributions" title: "Due contributions"
new: new:
@ -76,5 +78,11 @@ en:
subject: "ANSOL - Pagamento de quotas em atraso" subject: "ANSOL - Pagamento de quotas em atraso"
expired_60d_ago: expired_60d_ago:
subject: "ANSOL - Suspensão de inscrição iminente" subject: "ANSOL - Suspensão de inscrição iminente"
first_payment_confirmation:
subject: "ANSOL - Payment received: Welcome!"
payment_renewal_confirmation:
subject: "ANSOL - Payment received"
registration:
subject: "ANSOL - Registration approved: Payment required"
cancelled: cancelled:
subject: "ANSOL - Inscrição cancelada" subject: "ANSOL - Inscrição cancelada"

View File

@ -85,6 +85,7 @@ pt:
payment_on: Data de pagamento payment_on: Data de pagamento
payment_method: Método de pagamento payment_method: Método de pagamento
payment_reference: Referência payment_reference: Referência
should_send_notification: Enviar comprovativo de pagamento
edit: edit:
member: "Membro" member: "Membro"
title: "Editar dados de contribuição" title: "Editar dados de contribuição"
@ -113,6 +114,12 @@ pt:
subject: "ANSOL - Pagamento de quotas em atraso" subject: "ANSOL - Pagamento de quotas em atraso"
expired_60d_ago: expired_60d_ago:
subject: "ANSOL - Suspensão de inscrição iminente" subject: "ANSOL - Suspensão de inscrição iminente"
first_payment_confirmation:
subject: "ANSOL - Pagamento recebido: Mensagem de boas vindas"
payment_renewal_confirmation:
subject: "ANSOL - Pagamento de quotas recebido"
registration:
subject: "ANSOL - Inscrição aprovada: Pagamento inicial de quotas"
cancelled: cancelled:
subject: "ANSOL - Inscrição cancelada" subject: "ANSOL - Inscrição cancelada"
greetings: "Car(a/o/e) %{display_name}" greetings: "Car(a/o/e) %{display_name}"

View File

@ -32,7 +32,11 @@ Rails.application.routes.draw do
resource :board, only: [:edit, :update] resource :board, only: [:edit, :update]
resource :letters, only: [:create] resource :letters, only: [:create]
resources :payments, only: [:show] resources :payments, only: [:show] do
collection do
get :callback
end
end
resource :initial_setup, only: [:create, :show] resource :initial_setup, only: [:create, :show]
end end

View File

@ -8,6 +8,8 @@ SMTP_FROM_NAME=Example Name
IFTHENPAY_KEY=XXXX-XXXXXX IFTHENPAY_KEY=XXXX-XXXXXX
IFTHENPAY_ACCOUNTS=MBWAY|XXX-XXXXXX;XXXXX|XXX IFTHENPAY_ACCOUNTS=MBWAY|XXX-XXXXXX;XXXXX|XXX
IFTHENPAY_BO_KEY=XXXX-XXXX-XXXX-XXXX
IFTHENPAY_AP_KEY=my_anti_phishing_key
BASE_HOST=example.org BASE_HOST=example.org
RAILS_LOG_TO_STDOUT=true RAILS_LOG_TO_STDOUT=true