2024-02-14 03:54:42 +00:00
package payment
import (
"context"
2024-03-24 21:06:59 +00:00
"fmt"
2024-02-14 03:54:42 +00:00
"net/http"
"time"
"dev.tandem.ws/tandem/camper/pkg/auth"
"dev.tandem.ws/tandem/camper/pkg/database"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
2024-03-24 21:06:59 +00:00
"dev.tandem.ws/tandem/camper/pkg/redsys"
2024-02-14 03:54:42 +00:00
"dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
)
type AdminHandler struct {
}
func NewAdminHandler ( ) * AdminHandler {
return & AdminHandler { }
}
func ( h * AdminHandler ) Handler ( user * auth . User , company * auth . Company , conn * database . Conn ) http . Handler {
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
var head string
head , r . URL . Path = httplib . ShiftPath ( r . URL . Path )
switch head {
case "" :
switch r . Method {
case http . MethodGet :
servePaymentIndex ( w , r , user , company , conn )
default :
httplib . MethodNotAllowed ( w , r , http . MethodGet )
}
case "settings" :
head , r . URL . Path = httplib . ShiftPath ( r . URL . Path )
switch head {
case "" :
switch r . Method {
case http . MethodGet :
f := newSettingsForm ( user . Locale )
if err := f . FillFromDatabase ( r . Context ( ) , company , conn ) ; err != nil {
if ! database . ErrorIsNotFound ( err ) {
panic ( err )
}
}
f . MustRender ( w , r , user , company )
case http . MethodPut :
updatePaymentSettings ( w , r , user , company , conn )
default :
httplib . MethodNotAllowed ( w , r , http . MethodGet , http . MethodPut )
}
default :
http . NotFound ( w , r )
}
default :
if ! uuid . Valid ( head ) {
http . NotFound ( w , r )
return
}
payment , err := fetchPaymentDetails ( r . Context ( ) , conn , head , user . Locale )
if err != nil {
if database . ErrorIsNotFound ( err ) {
http . NotFound ( w , r )
return
}
panic ( err )
}
2024-03-24 21:06:59 +00:00
h . paymentHandler ( user , company , conn , payment ) . ServeHTTP ( w , r )
2024-02-14 03:54:42 +00:00
}
} )
}
2024-03-24 21:06:59 +00:00
func ( h * AdminHandler ) paymentHandler ( user * auth . User , company * auth . Company , conn * database . Conn , payment * paymentDetails ) http . Handler {
2024-02-14 03:54:42 +00:00
return http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
var head string
head , r . URL . Path = httplib . ShiftPath ( r . URL . Path )
switch head {
case "" :
switch r . Method {
case http . MethodGet :
2024-03-24 21:06:59 +00:00
if payment . Status == StatusPreAuthenticated {
var err error
if err = conn . QueryRow ( r . Context ( ) , "select environment from redsys where company_id = $1" , company . ID ) . Scan ( & payment . Environment ) ; err != nil && ! database . ErrorIsNotFound ( err ) {
panic ( err )
}
payment . AcceptPreauthRequest , err = payment . createRequest ( r , user , company , conn , redsys . TransactionTypePreauthConfirm )
if err != nil {
panic ( err )
}
payment . VoidPreauthRequest , err = payment . createRequest ( r , user , company , conn , redsys . TransactionTypePreauthVoid )
if err != nil {
panic ( err )
}
}
2024-02-14 03:54:42 +00:00
payment . MustRender ( w , r , user , company )
default :
httplib . MethodNotAllowed ( w , r , http . MethodGet )
}
default :
http . NotFound ( w , r )
}
} )
}
func servePaymentIndex ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company , conn * database . Conn ) {
2024-05-03 18:09:07 +00:00
filters := newFilterForm ( r . Context ( ) , conn , company , user . Locale )
if err := filters . Parse ( r ) ; err != nil {
http . Error ( w , err . Error ( ) , http . StatusBadRequest )
return
}
payments , err := collectPaymentEntries ( r . Context ( ) , company , conn , user . Locale , filters )
2024-02-14 03:54:42 +00:00
if err != nil {
panic ( err )
}
page := & paymentIndex {
2024-05-03 18:09:07 +00:00
Payments : filters . buildCursor ( payments ) ,
Filters : filters ,
2024-02-14 03:54:42 +00:00
}
page . MustRender ( w , r , user , company )
}
type paymentEntry struct {
2024-05-03 18:09:07 +00:00
ID int
2024-02-14 03:54:42 +00:00
URL string
Reference string
DownPayment string
Total string
Status string
StatusLabel string
CreatedAt time . Time
}
2024-05-03 18:09:07 +00:00
func collectPaymentEntries ( ctx context . Context , company * auth . Company , conn * database . Conn , locale * locale . Locale , filters * filterForm ) ( [ ] * paymentEntry , error ) {
where , args := filters . BuildQuery ( [ ] interface { } { locale . Language } )
rows , err := conn . Query ( ctx , fmt . Sprintf ( `
select payment_id
, ' / admin / payments / ' || payment . slug
2024-02-14 03:54:42 +00:00
, payment . reference
, to_price ( payment . down_payment , decimal_digits )
, to_price ( total , decimal_digits )
, payment . payment_status
, coalesce ( payment_status_i18n . name , payment_status . name )
, created_at
from payment
join currency using ( currency_code )
join payment_status using ( payment_status )
left join payment_status_i18n on payment_status_i18n . payment_status = payment . payment_status
2024-05-03 18:09:07 +00:00
and payment_status_i18n . lang_tag = $ 1
where ( % s )
2024-02-14 03:54:42 +00:00
order by created_at desc
2024-05-03 18:09:07 +00:00
, payment_id
limit % d
` , where , filters . PerPage ( ) + 1 ) , args ... )
2024-02-14 03:54:42 +00:00
if err != nil {
return nil , err
}
defer rows . Close ( )
var payments [ ] * paymentEntry
for rows . Next ( ) {
entry := & paymentEntry { }
if err = rows . Scan (
2024-05-03 18:09:07 +00:00
& entry . ID ,
2024-02-14 03:54:42 +00:00
& entry . URL ,
& entry . Reference ,
& entry . DownPayment ,
& entry . Total ,
& entry . Status ,
& entry . StatusLabel ,
& entry . CreatedAt ,
) ; err != nil {
return nil , err
}
payments = append ( payments , entry )
}
return payments , nil
}
type paymentIndex struct {
Payments [ ] * paymentEntry
2024-05-03 18:09:07 +00:00
Filters * filterForm
2024-02-14 03:54:42 +00:00
}
func ( page * paymentIndex ) MustRender ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company ) {
2024-05-03 18:09:07 +00:00
if httplib . IsHTMxRequest ( r ) && page . Filters . Paginated ( ) {
template . MustRenderAdminNoLayout ( w , r , user , company , "payment/results.gohtml" , page )
} else {
template . MustRenderAdminFiles ( w , r , user , company , page , "payment/index.gohtml" , "payment/results.gohtml" )
}
2024-02-14 03:54:42 +00:00
}
type paymentDetails struct {
2024-03-24 21:06:59 +00:00
ID int
Slug string
Reference string
CampsiteType string
ArrivalDate time . Time
DepartureDate time . Time
NumNights int
NumAdults int
NumTeenagers int
NumChildren int
NumDogs int
SubtotalTouristTax string
Total string
DownPaymentPercent int
DownPayment string
ZonePreferences string
ACSICard bool
Status string
StatusLabel string
CreatedAt time . Time
UpdatedAt time . Time
Options [ ] * paymentOption
Customer * paymentCustomer
Environment string
AcceptPreauthRequest * redsys . SignedRequest
VoidPreauthRequest * redsys . SignedRequest
2024-02-14 03:54:42 +00:00
}
type paymentOption struct {
Label string
Units int
}
type paymentCustomer struct {
FullName string
Address string
PostalCode string
City string
Country string
Email string
Phone string
Language string
}
func fetchPaymentDetails ( ctx context . Context , conn * database . Conn , slug string , locale * locale . Locale ) ( * paymentDetails , error ) {
row := conn . QueryRow ( ctx , `
select payment_id
2024-03-24 21:06:59 +00:00
, payment . slug
2024-02-14 03:54:42 +00:00
, payment . reference
, coalesce ( campsite_type_i18n . name , campsite_type . name )
, arrival_date
, departure_date
, departure_date - arrival_date
, number_adults
, number_teenagers
, number_children
, number_dogs
, to_price ( subtotal_tourist_tax , decimal_digits )
, to_price ( total , decimal_digits )
, ( down_payment_percent * 100 ) : : integer
, to_price ( payment . down_payment , decimal_digits )
, zone_preferences
2024-03-14 21:08:01 +00:00
, acsi_card
2024-02-14 03:54:42 +00:00
, payment . payment_status
, coalesce ( payment_status_i18n . name , payment_status . name )
, created_at
, updated_at
from payment
join currency using ( currency_code )
join campsite_type using ( campsite_type_id )
join payment_status using ( payment_status )
left join payment_status_i18n on payment_status_i18n . payment_status = payment . payment_status
and payment_status_i18n . lang_tag = $ 2
left join campsite_type_i18n on campsite_type_i18n . campsite_type_id = campsite_type . campsite_type_id
and campsite_type_i18n . lang_tag = $ 2
where payment . slug = $ 1
` , slug , locale . Language )
details := & paymentDetails { }
if err := row . Scan (
& details . ID ,
2024-03-24 21:06:59 +00:00
& details . Slug ,
2024-02-14 03:54:42 +00:00
& details . Reference ,
& details . CampsiteType ,
& details . ArrivalDate ,
& details . DepartureDate ,
& details . NumNights ,
& details . NumAdults ,
& details . NumTeenagers ,
& details . NumChildren ,
& details . NumDogs ,
& details . SubtotalTouristTax ,
& details . Total ,
& details . DownPaymentPercent ,
& details . DownPayment ,
& details . ZonePreferences ,
2024-03-14 21:08:01 +00:00
& details . ACSICard ,
2024-02-14 03:54:42 +00:00
& details . Status ,
& details . StatusLabel ,
& details . CreatedAt ,
& details . UpdatedAt ,
) ; err != nil {
return nil , err
}
var err error
details . Options , err = fetchPaymentOptions ( ctx , conn , details . ID , locale )
if err != nil && ! database . ErrorIsNotFound ( err ) {
return nil , err
}
details . Customer , err = fetchPaymentCustomer ( ctx , conn , details . ID , locale )
if err != nil && ! database . ErrorIsNotFound ( err ) {
return nil , err
}
return details , nil
}
func fetchPaymentOptions ( ctx context . Context , conn * database . Conn , paymentID int , locale * locale . Locale ) ( [ ] * paymentOption , error ) {
rows , err := conn . Query ( ctx , `
select coalesce ( option_i18n . name , option . name )
, units
from payment_option
join campsite_type_option as option using ( campsite_type_option_id )
left join campsite_type_option_i18n as option_i18n on option_i18n . campsite_type_option_id = option . campsite_type_option_id
and option_i18n . lang_tag = $ 2
where payment_id = $ 1
` , paymentID , locale . Language )
if err != nil {
return nil , err
}
defer rows . Close ( )
var options [ ] * paymentOption
for rows . Next ( ) {
option := & paymentOption { }
if err = rows . Scan ( & option . Label , & option . Units ) ; err != nil {
return nil , err
}
options = append ( options , option )
}
return options , nil
}
func fetchPaymentCustomer ( ctx context . Context , conn * database . Conn , paymentID int , locale * locale . Locale ) ( * paymentCustomer , error ) {
row := conn . QueryRow ( ctx , `
select full_name
, address
, postal_code
, city
, coalesce ( country_i18n . name , country . name )
, email
, phone : : text
, language . endonym
from payment_customer
join country using ( country_code )
left join country_i18n on country . country_code = country_i18n . country_code
and country_i18n . lang_tag = $ 2
join language on payment_customer . lang_tag = language . lang_tag
where payment_id = $ 1
` , paymentID , locale . Language )
customer := & paymentCustomer { }
if err := row . Scan (
& customer . FullName ,
& customer . Address ,
& customer . PostalCode ,
& customer . City ,
& customer . Country ,
& customer . Email ,
& customer . Phone ,
& customer . Language ,
) ; err != nil {
return nil , err
}
return customer , nil
}
2024-03-24 21:06:59 +00:00
func ( f * paymentDetails ) createRequest ( r * http . Request , user * auth . User , company * auth . Company , conn * database . Conn , transactionType redsys . TransactionType ) ( * redsys . SignedRequest , error ) {
schema := httplib . Protocol ( r )
authority := httplib . Host ( r )
paymentURL := fmt . Sprintf ( "%s://%s/admin/payments/%s" , schema , authority , f . Slug )
request := redsys . Request {
TransactionType : transactionType ,
Amount : f . DownPayment ,
OrderNumber : f . Reference ,
SuccessURL : paymentURL ,
FailureURL : paymentURL ,
NotificationURL : fmt . Sprintf ( "%s/notification" , publicBaseURL ( r , user , f . Slug ) ) ,
ConsumerLanguage : user . Locale . Language ,
}
return request . Sign ( r . Context ( ) , conn , company )
}
2024-02-14 03:54:42 +00:00
func ( f * paymentDetails ) MustRender ( w http . ResponseWriter , r * http . Request , user * auth . User , company * auth . Company ) {
template . MustRenderAdmin ( w , r , user , company , "payment/details.gohtml" , f )
}