Add ready_payment function and use their slug as URL

Now that the payments have slug, i can use them in the URL to show the
actual data of a payment, and kickstart the payment process with Redsys.
This commit is contained in:
jordi fita mas 2024-02-12 18:06:17 +01:00
parent 148d9075da
commit 15dde3f491
22 changed files with 785 additions and 307 deletions

View File

@ -19,10 +19,11 @@ create table payment_customer (
country_code country_code not null references country,
email email not null,
phone packed_phone_number not null,
acsi_card boolean not null
acsi_card boolean not null,
lang_tag text not null references language
);
grant select, insert on table payment_customer to guest;
grant select, insert, update on table payment_customer to guest;
grant select, insert, update on table payment_customer to employee;
grant select, insert, update, delete on table payment_customer to admin;

56
deploy/ready_payment.sql Normal file
View File

@ -0,0 +1,56 @@
-- Deploy camper:ready_payment to pg
-- requires: roles
-- requires: schema_camper
-- requires: payment
-- requires: payment_customer
-- requires: country_code
-- requires: email
-- requires: extension_pg_libphonenumber
begin;
set search_path to camper, public;
create or replace function ready_payment(payment_slug uuid, customer_name text, customer_address text, customer_post_code text, customer_city text, customer_country_code country_code, customer_email email, customer_phone text, customer_lang_tag text, customer_acsi_card boolean) returns integer as
$$
declare
pid integer;
begin
update payment
set payment_status = 'pending'
, updated_at = current_timestamp
where slug = payment_slug
and payment_status = 'draft'
returning payment_id into pid
;
if pid is null then
raise check_violation using message = 'insert or update on table "payment" violates check constraint "payment_is_draft"';
end if;
insert into payment_customer (payment_id, full_name, address, postal_code, city, country_code, email, phone, acsi_card, lang_tag)
values (pid, customer_name, customer_address, customer_post_code, customer_city, customer_country_code, customer_email, parse_packed_phone_number(customer_phone, customer_country_code), customer_acsi_card, customer_lang_tag)
on conflict (payment_id) do update
set full_name = excluded.full_name
, address = excluded.address
, postal_code = excluded.postal_code
, city = excluded.city
, country_code = excluded.country_code
, email = excluded.email
, phone = excluded.phone
, acsi_card = excluded.acsi_card
, lang_tag = excluded.lang_tag
;
return pid;
end;
$$
language plpgsql
;
revoke execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) from public;
grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to guest;
grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to employee;
grant execute on function ready_payment(uuid, text, text, text, text, country_code, email, text, text, boolean) to admin;
commit;

View File

@ -17,6 +17,7 @@ import (
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/legal"
"dev.tandem.ws/tandem/camper/pkg/location"
"dev.tandem.ws/tandem/camper/pkg/payment"
"dev.tandem.ws/tandem/camper/pkg/services"
"dev.tandem.ws/tandem/camper/pkg/surroundings"
"dev.tandem.ws/tandem/camper/pkg/template"
@ -29,6 +30,7 @@ type publicHandler struct {
campsite *campsite.PublicHandler
legal *legal.PublicHandler
location *location.PublicHandler
payment *payment.PublicHandler
services *services.PublicHandler
surroundings *surroundings.PublicHandler
}
@ -41,6 +43,7 @@ func newPublicHandler() *publicHandler {
campsite: campsite.NewPublicHandler(),
legal: legal.NewPublicHandler(),
location: location.NewPublicHandler(),
payment: payment.NewPublicHandler(),
services: services.NewPublicHandler(),
surroundings: surroundings.NewPublicHandler(),
}
@ -65,6 +68,8 @@ func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *da
h.legal.Handler(user, company, conn).ServeHTTP(w, r)
case "location":
h.location.Handler(user, company, conn).ServeHTTP(w, r)
case "payments":
h.payment.Handler(user, company, conn).ServeHTTP(w, r)
case "services":
h.services.Handler(user, company, conn).ServeHTTP(w, r)
case "surroundings":

View File

@ -6,98 +6,46 @@
package booking
import (
"fmt"
"net/http"
"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/redsys"
"dev.tandem.ws/tandem/camper/pkg/template"
)
type paymentPage struct {
*template.PublicPage
Environment string
Request *redsys.SignedRequest
func requestPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f, err := newBookingForm(r, company, conn, user.Locale)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
panic(err)
} else if !ok {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
page := newPublicPageWithForm(f)
page.MustRender(w, r, user, company, conn)
return
}
func newPaymentPage(request *redsys.SignedRequest) *paymentPage {
return &paymentPage{
PublicPage: template.NewPublicPage(),
Request: request,
}
}
func (p *paymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
if err := conn.QueryRow(r.Context(), "select environment from redsys where company_id = $1", company.ID).Scan(&p.Environment); err != nil && !database.ErrorIsNotFound(err) {
_, err = conn.ReadyPayment(
r.Context(),
f.PaymentSlug.Val,
f.Customer.FullName.Val,
f.Customer.Address.Val,
f.Customer.PostalCode.Val,
f.Customer.City.Val,
f.Customer.Country.String(),
f.Customer.Email.Val,
f.Customer.Phone.Val,
user.Locale.Language,
f.Customer.ACSICard.Checked,
)
if err != nil {
panic(err)
}
template.MustRenderPublic(w, r, user, company, "payment/request.gohtml", p)
}
func handleSuccessfulPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newSuccessfulPaymentPage()
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
http.NotFound(w, r)
}
}
type successfulPaymentPage struct {
*template.PublicPage
}
func newSuccessfulPaymentPage() *successfulPaymentPage {
return &successfulPaymentPage{
PublicPage: template.NewPublicPage(),
}
}
func (p *successfulPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "payment/success.gohtml", p)
}
func handleFailedPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newFailedPaymentPage()
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
http.NotFound(w, r)
}
}
type failedPaymentPage struct {
*template.PublicPage
}
func newFailedPaymentPage() *failedPaymentPage {
return &failedPaymentPage{
PublicPage: template.NewPublicPage(),
}
}
func (p *failedPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublic(w, r, user, company, "payment/failure.gohtml", p)
httplib.Redirect(w, r, fmt.Sprintf("/%s/payments/%s", user.Locale.Language, f.PaymentSlug.Val), http.StatusSeeOther)
}

View File

@ -7,8 +7,6 @@ package booking
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"net/http"
@ -20,7 +18,6 @@ import (
"dev.tandem.ws/tandem/camper/pkg/form"
httplib "dev.tandem.ws/tandem/camper/pkg/http"
"dev.tandem.ws/tandem/camper/pkg/locale"
"dev.tandem.ws/tandem/camper/pkg/redsys"
"dev.tandem.ws/tandem/camper/pkg/template"
)
@ -47,66 +44,16 @@ func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *da
}
page.MustRender(w, r, user, company, conn)
case http.MethodPost:
makeReservation(w, r, user, company, conn)
requestPayment(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
}
case "success":
handleSuccessfulPayment(w, r, user, company, conn)
case "failure":
handleFailedPayment(w, r, user, company, conn)
default:
http.NotFound(w, r)
}
})
}
func makeReservation(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
f, err := newBookingForm(r, company, conn, user.Locale)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if ok, err := f.Valid(r.Context(), conn, user.Locale); err != nil {
panic(err)
} else if !ok {
if !httplib.IsHTMxRequest(r) {
w.WriteHeader(http.StatusUnprocessableEntity)
}
page := newPublicPageWithForm(f)
page.MustRender(w, r, user, company, conn)
return
}
schema := httplib.Protocol(r)
authority := httplib.Host(r)
baseURL := fmt.Sprintf("%s://%s/%s/booking", schema, authority, user.Locale.Language)
request := &redsys.Request{
TransactionType: redsys.TransactionTypeCharge,
Amount: f.Cart.Total,
OrderNumber: randomOrderNumber(),
Product: "Test Booking",
SuccessURL: fmt.Sprintf("%s/success", baseURL),
FailureURL: fmt.Sprintf("%s/failure", baseURL),
NotificationURL: fmt.Sprintf("%s/notification", baseURL),
ConsumerLanguage: user.Locale.Language,
}
signed, err := redsys.SignRequest(r.Context(), conn, company, request)
if err != nil {
panic(err)
}
page := newPaymentPage(signed)
page.MustRender(w, r, user, company, conn)
}
func randomOrderNumber() string {
bytes := make([]byte, 6)
if _, err := rand.Read(bytes); err != nil {
panic(err)
}
return hex.EncodeToString(bytes)
}
type publicPage struct {
*template.PublicPage
Form *bookingForm
@ -272,6 +219,9 @@ func (f *bookingForm) Valid(ctx context.Context, conn *database.Conn, l *locale.
if f.Customer == nil {
return false, errors.New("no customer fields")
}
if f.Cart == nil {
return false, errors.New("no booking cart")
}
v.CheckSelectedOptions(f.CampsiteType, l.GettextNoop("Selected campsite type is not valid."))
f.Dates.Valid(v, l)

View File

@ -357,3 +357,7 @@ func (c *Conn) DraftPayment(ctx context.Context, paymentSlug string, arrivalDate
}
return c.GetText(ctx, "select draft_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlugParam, arrivalDate, departureDate, campsiteTypeSlug, numAdults, numTeenagers, numChildren, numDogs, zonePreferences, options)
}
func (c *Conn) ReadyPayment(ctx context.Context, paymentSlug string, customerName string, customerAddress string, customerPostCode string, customerCity string, customerCountryCode string, customerEmail string, customerPhone string, customerLangTag language.Tag, acsiCard bool) (int, error) {
return c.GetInt(ctx, "select ready_payment($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", paymentSlug, customerName, customerAddress, customerPostCode, customerCity, customerCountryCode, customerEmail, customerPhone, customerLangTag, acsiCard)
}

204
pkg/payment/public.go Normal file
View File

@ -0,0 +1,204 @@
package payment
import (
"context"
"fmt"
"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/redsys"
"dev.tandem.ws/tandem/camper/pkg/template"
"dev.tandem.ws/tandem/camper/pkg/uuid"
)
type PublicHandler struct {
}
func NewPublicHandler() *PublicHandler {
return &PublicHandler{}
}
func (h *PublicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var paymentSlug string
paymentSlug, r.URL.Path = httplib.ShiftPath(r.URL.Path)
if !uuid.Valid(paymentSlug) {
http.NotFound(w, r)
return
}
payment, err := fetchPayment(r.Context(), conn, paymentSlug)
if err != nil {
if database.ErrorIsNotFound(err) {
http.NotFound(w, r)
return
}
panic(err)
}
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newPaymentPage(payment)
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
case "success":
handleSuccessfulPayment(w, r, user, company, conn, payment)
case "failure":
handleFailedPayment(w, r, user, company, conn, payment)
default:
http.NotFound(w, r)
}
})
}
func fetchPayment(ctx context.Context, conn *database.Conn, paymentSlug string) (*Payment, error) {
row := conn.QueryRow(ctx, `
select payment_id
, payment.slug::text
, payment.created_at
, to_price(total, decimal_digits)
from payment
join company using (company_id)
join currency using (currency_code)
where payment.slug = $1
and payment_status <> 'draft'
`, paymentSlug)
payment := &Payment{}
if err := row.Scan(&payment.ID, &payment.Slug, &payment.CreateTime, &payment.Total); err != nil {
return nil, err
}
return payment, nil
}
type Payment struct {
ID int
Slug string
Total string
CreateTime time.Time
}
func (payment *Payment) createRequest(r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) (*redsys.SignedRequest, error) {
schema := httplib.Protocol(r)
authority := httplib.Host(r)
baseURL := fmt.Sprintf("%s://%s/%s/payments/%s", schema, authority, user.Locale.Language, payment.Slug)
request := &redsys.Request{
TransactionType: redsys.TransactionTypeCharge,
Amount: payment.Total,
OrderNumber: payment.OrderNumber(),
Product: user.Locale.Pgettext("Campsite Booking", "order product name"),
SuccessURL: fmt.Sprintf("%s/success", baseURL),
FailureURL: fmt.Sprintf("%s/failure", baseURL),
NotificationURL: fmt.Sprintf("%s/notification", baseURL),
ConsumerLanguage: user.Locale.Language,
}
return redsys.SignRequest(r.Context(), conn, company, request)
}
func (payment *Payment) OrderNumber() string {
return fmt.Sprintf("%08d%s", payment.ID, payment.Slug[:4])
}
type paymentPage struct {
*template.PublicPage
Environment string
Payment *Payment
Request *redsys.SignedRequest
}
func newPaymentPage(payment *Payment) *paymentPage {
return &paymentPage{
PublicPage: template.NewPublicPage(),
Payment: payment,
}
}
func (p *paymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
request, err := p.Payment.createRequest(r, user, company, conn)
if err != nil {
panic(err)
}
p.Request = request
if err := conn.QueryRow(r.Context(), "select environment from redsys where company_id = $1", company.ID).Scan(&p.Environment); err != nil && !database.ErrorIsNotFound(err) {
panic(err)
}
template.MustRenderPublic(w, r, user, company, "payment/request.gohtml", p)
}
func handleSuccessfulPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, payment *Payment) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newSuccessfulPaymentPage(payment)
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
http.NotFound(w, r)
}
}
type successfulPaymentPage struct {
*template.PublicPage
Payment *Payment
}
func newSuccessfulPaymentPage(payment *Payment) *successfulPaymentPage {
return &successfulPaymentPage{
PublicPage: template.NewPublicPage(),
Payment: payment,
}
}
func (p *successfulPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublicFiles(w, r, user, company, p, "payment/success.gohtml", "payment/details.gohtml")
}
func handleFailedPayment(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn, payment *Payment) {
var head string
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
switch head {
case "":
switch r.Method {
case http.MethodGet:
page := newFailedPaymentPage(payment)
page.MustRender(w, r, user, company, conn)
default:
httplib.MethodNotAllowed(w, r, http.MethodGet)
}
default:
http.NotFound(w, r)
}
}
type failedPaymentPage struct {
*template.PublicPage
Payment *Payment
}
func newFailedPaymentPage(payment *Payment) *failedPaymentPage {
return &failedPaymentPage{
PublicPage: template.NewPublicPage(),
Payment: payment,
}
}
func (p *failedPaymentPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
p.Setup(r, user, company, conn)
template.MustRenderPublicFiles(w, r, user, company, p, "payment/failure.gohtml", "payment/details.gohtml")
}

135
po/ca.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-02-12 05:10+0100\n"
"POT-Creation-Date: 2024-02-12 17:49+0100\n"
"PO-Revision-Date: 2024-02-06 10:04+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Catalan <ca@dodds.net>\n"
@ -60,28 +60,56 @@ msgid "Zone 1"
msgstr "Zona 1"
#: web/templates/public/payment/success.gohtml:6
#: web/templates/public/payment/success.gohtml:11
msgctxt "title"
msgid "Payment Successful"
msgstr "Pagament amb èxit"
#: web/templates/public/payment/request.gohtml:6
#: web/templates/public/payment/request.gohtml:11
msgctxt "title"
msgid "Payment"
msgstr "Pagament"
#: web/templates/public/payment/success.gohtml:12
msgid "We have received the payment. Thank you."
msgstr "Hem rebut el vostre pagament. Gràcies."
#: web/templates/public/payment/request.gohtml:23
#: web/templates/public/payment/request.gohtml:6
msgctxt "title"
msgid "Booking Payment"
msgstr "Pagament de la reserva"
#: web/templates/public/payment/request.gohtml:18
msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys."
msgstr "Gràcies per la vostra reserva. Feu clic al botó de sota per pagar amb targeta de crèdit via Servired/Redsys."
#: web/templates/public/payment/request.gohtml:27
msgctxt "action"
msgid "Pay"
msgstr "Paga"
msgid "Pay with credit card"
msgstr "Paga amb targeta de crèdit"
#: web/templates/public/payment/request.gohtml:30
msgid "Please, wait until we redirect you to the payment page."
msgstr "Espereu mentre us enviem a la pàgina de pagament."
#: web/templates/public/payment/failure.gohtml:6
#: web/templates/public/payment/failure.gohtml:11
msgctxt "title"
msgid "Payment Failed"
msgstr "Ha fallat el pagament"
#: web/templates/public/payment/failure.gohtml:12
msgid "We could not process the payment. Please, contact us for support."
msgstr "No hem pogut processar el pagament. Poseu-vos en contacte amb nosaltres per solucionar el problema."
#: web/templates/public/payment/details.gohtml:4
msgctxt "title"
msgid "Order Number"
msgstr "Número de comanda"
#: web/templates/public/payment/details.gohtml:8
msgctxt "title"
msgid "Date"
msgstr "Data"
#: web/templates/public/payment/details.gohtml:12
msgctxt "title"
msgid "Total"
msgstr "Total"
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
@ -1951,6 +1979,11 @@ msgstr "Estat"
msgid "No booking found."
msgstr "No sha trobat cap reserva."
#: pkg/payment/public.go:97
msgctxt "order product name"
msgid "Campsite Booking"
msgstr "Reserva de càmping"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:412
@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type."
msgstr "La imatge de la diapositiva ha de ser un mèdia de tipus imatge."
#: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217
#: pkg/booking/public.go:583
#: pkg/booking/public.go:533
msgid "Email can not be empty."
msgstr "No podeu deixar el correu-e en blanc."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218
#: pkg/booking/public.go:584
#: pkg/booking/public.go:534
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com."
@ -2206,8 +2239,8 @@ msgctxt "header"
msgid "Children (aged 2 to 10)"
msgstr "Mainada (entre 2 i 10 anys)"
#: pkg/campsite/admin.go:275 pkg/booking/public.go:224
#: pkg/booking/public.go:276
#: pkg/campsite/admin.go:275 pkg/booking/public.go:171
#: pkg/booking/public.go:226
msgid "Selected campsite type is not valid."
msgstr "El tipus dallotjament escollit no és vàlid."
@ -2363,7 +2396,7 @@ msgstr "No podeu deixar ladreça de lenllaç en blanc."
msgid "This web address is not valid. It should be like https://domain.com/."
msgstr "Aquesta adreça web no és vàlida. Hauria de ser similar a https://domini.com/."
#: pkg/company/admin.go:200 pkg/booking/public.go:570
#: pkg/company/admin.go:200 pkg/booking/public.go:520
msgid "Selected country is not valid."
msgstr "El país escollit no és vàlid."
@ -2383,11 +2416,11 @@ msgstr "No podeu deixar el NIF en blanc."
msgid "This VAT number is not valid."
msgstr "Aquest NIF no és vàlid."
#: pkg/company/admin.go:212 pkg/booking/public.go:586
#: pkg/company/admin.go:212 pkg/booking/public.go:536
msgid "Phone can not be empty."
msgstr "No podeu deixar el telèfon en blanc."
#: pkg/company/admin.go:213 pkg/booking/public.go:587
#: pkg/company/admin.go:213 pkg/booking/public.go:537
msgid "This phone number is not valid."
msgstr "Aquest número de telèfon no és vàlid."
@ -2407,7 +2440,7 @@ msgstr "No podeu deixar la província en blanc."
msgid "Postal code can not be empty."
msgstr "No podeu deixar el codi postal en blanc."
#: pkg/company/admin.go:227 pkg/booking/public.go:579
#: pkg/company/admin.go:227 pkg/booking/public.go:529
msgid "This postal code is not valid."
msgstr "Aquest codi postal no és vàlid."
@ -2534,127 +2567,135 @@ msgstr "La integració escollida no és vàlida."
msgid "The merchant key is not valid."
msgstr "Aquesta clau del comerç no és vàlid."
#: pkg/booking/public.go:325 pkg/booking/public.go:354
#: pkg/booking/public.go:275 pkg/booking/public.go:304
msgid "Arrival date must be a valid date."
msgstr "La data darribada ha de ser una data vàlida."
#: pkg/booking/public.go:339 pkg/booking/public.go:361
#: pkg/booking/public.go:289 pkg/booking/public.go:311
msgid "Departure date must be a valid date."
msgstr "La data de sortida ha de ser una data vàlida."
#: pkg/booking/public.go:353
#: pkg/booking/public.go:303
msgid "Arrival date can not be empty"
msgstr "No podeu deixar la data darribada en blanc."
#: pkg/booking/public.go:355
#: pkg/booking/public.go:305
#, c-format
msgid "Arrival date must be %s or after."
msgstr "La data darribada ha de ser igual o posterior a %s."
#: pkg/booking/public.go:356
#: pkg/booking/public.go:306
#, c-format
msgid "Arrival date must be %s or before."
msgstr "La data darribada ha de ser anterior o igual a %s."
#: pkg/booking/public.go:360
#: pkg/booking/public.go:310
msgid "Departure date can not be empty"
msgstr "No podeu deixar la data de sortida en blanc."
#: pkg/booking/public.go:362
#: pkg/booking/public.go:312
#, c-format
msgid "Departure date must be %s or after."
msgstr "La data de sortida ha de ser igual o posterior a %s."
#: pkg/booking/public.go:363
#: pkg/booking/public.go:313
#, c-format
msgid "Departure date must be %s or before."
msgstr "La data de sortida ha de ser anterior o igual a %s."
#: pkg/booking/public.go:405
#: pkg/booking/public.go:355
#, c-format
msgid "There can be at most %d guests in this accommodation."
msgstr "Hi poden haver com a màxim %d convidats a aquest allotjament."
#: pkg/booking/public.go:425
#: pkg/booking/public.go:375
msgid "Number of adults can not be empty"
msgstr "No podeu deixar el número dadults en blanc."
#: pkg/booking/public.go:426
#: pkg/booking/public.go:376
msgid "Number of adults must be an integer."
msgstr "El número dadults ha de ser enter."
#: pkg/booking/public.go:427
#: pkg/booking/public.go:377
msgid "There must be at least one adult."
msgstr "Hi ha dhaver com a mínim un adult."
#: pkg/booking/public.go:430
#: pkg/booking/public.go:380
msgid "Number of teenagers can not be empty"
msgstr "No podeu deixar el número dadolescents en blanc."
#: pkg/booking/public.go:431
#: pkg/booking/public.go:381
msgid "Number of teenagers must be an integer."
msgstr "El número dadolescents ha de ser enter."
#: pkg/booking/public.go:432
#: pkg/booking/public.go:382
msgid "Number of teenagers can not be negative."
msgstr "El número dadolescents no pot ser negatiu."
#: pkg/booking/public.go:435
#: pkg/booking/public.go:385
msgid "Number of children can not be empty"
msgstr "No podeu deixar el número de nens en blanc."
#: pkg/booking/public.go:436
#: pkg/booking/public.go:386
msgid "Number of children must be an integer."
msgstr "El número de nens ha de ser enter."
#: pkg/booking/public.go:437
#: pkg/booking/public.go:387
msgid "Number of children can not be negative."
msgstr "El número de nens no pot ser negatiu."
#: pkg/booking/public.go:440
#: pkg/booking/public.go:390
msgid "Number of dogs can not be empty"
msgstr "No podeu deixar el número de gossos en blanc."
#: pkg/booking/public.go:441
#: pkg/booking/public.go:391
msgid "Number of dogs must be an integer."
msgstr "El número de gossos ha de ser enter."
#: pkg/booking/public.go:442
#: pkg/booking/public.go:392
msgid "Number of dogs can not be negative."
msgstr "El número de gossos no pot ser negatiu."
#: pkg/booking/public.go:513
#: pkg/booking/public.go:463
#, c-format
msgid "%s can not be empty"
msgstr "No podeu deixar %s en blanc."
#: pkg/booking/public.go:514
#: pkg/booking/public.go:464
#, c-format
msgid "%s must be an integer."
msgstr "%s ha de ser un número enter."
#: pkg/booking/public.go:515
#: pkg/booking/public.go:465
#, c-format
msgid "%s must be %d or greater."
msgstr "El valor de %s ha de ser com a mínim %d."
#: pkg/booking/public.go:516
#: pkg/booking/public.go:466
#, c-format
msgid "%s must be at most %d."
msgstr "El valor de %s ha de ser com a màxim %d."
#: pkg/booking/public.go:574
#: pkg/booking/public.go:524
msgid "Full name can not be empty."
msgstr "No podeu deixar el nom i els cognoms en blanc."
#: pkg/booking/public.go:575
#: pkg/booking/public.go:525
msgid "Full name must have at least one letter."
msgstr "El nom i els cognoms han de tenir com a mínim una lletra."
#: pkg/booking/public.go:592
#: pkg/booking/public.go:542
msgid "It is mandatory to agree to the reservation conditions."
msgstr "És obligatori acceptar les condicions de reserves."
#~ msgctxt "title"
#~ msgid "Payment"
#~ msgstr "Pagament"
#~ msgctxt "action"
#~ msgid "Pay"
#~ msgstr "Paga"
#~ msgctxt "input"
#~ msgid "Check-in Date"
#~ msgstr "Data dentrada"

135
po/es.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-02-12 05:10+0100\n"
"POT-Creation-Date: 2024-02-12 17:49+0100\n"
"PO-Revision-Date: 2024-02-06 10:04+0100\n"
"Last-Translator: jordi fita mas <jordi@tandem.blog>\n"
"Language-Team: Spanish <es@tp.org.es>\n"
@ -60,28 +60,56 @@ msgid "Zone 1"
msgstr "Zona 1"
#: web/templates/public/payment/success.gohtml:6
#: web/templates/public/payment/success.gohtml:11
msgctxt "title"
msgid "Payment Successful"
msgstr "Pago con éxito"
#: web/templates/public/payment/request.gohtml:6
#: web/templates/public/payment/request.gohtml:11
msgctxt "title"
msgid "Payment"
msgstr "Pago"
#: web/templates/public/payment/success.gohtml:12
msgid "We have received the payment. Thank you."
msgstr "Hemos recibido su pago. Gracias."
#: web/templates/public/payment/request.gohtml:23
#: web/templates/public/payment/request.gohtml:6
msgctxt "title"
msgid "Booking Payment"
msgstr "Pago de la reserva"
#: web/templates/public/payment/request.gohtml:18
msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys."
msgstr "Gracias por vuestro pedido. Haced clic al botón de abajo para pagar con tarjeta de crédito vía Servired/Redsys."
#: web/templates/public/payment/request.gohtml:27
msgctxt "action"
msgid "Pay"
msgstr "Pagar"
msgid "Pay with credit card"
msgstr "Pagar con tarjeta de crédito"
#: web/templates/public/payment/request.gohtml:30
msgid "Please, wait until we redirect you to the payment page."
msgstr "Esperad mientras se os redirige a la página de pago."
#: web/templates/public/payment/failure.gohtml:6
#: web/templates/public/payment/failure.gohtml:11
msgctxt "title"
msgid "Payment Failed"
msgstr "Pago fallido"
#: web/templates/public/payment/failure.gohtml:12
msgid "We could not process the payment. Please, contact us for support."
msgstr "No hemos podido procesar su pago. Póngase en contacto con nosotros para solucionar el problema."
#: web/templates/public/payment/details.gohtml:4
msgctxt "title"
msgid "Order Number"
msgstr "Número de pedido"
#: web/templates/public/payment/details.gohtml:8
msgctxt "title"
msgid "Date"
msgstr "Fecha"
#: web/templates/public/payment/details.gohtml:12
msgctxt "title"
msgid "Total"
msgstr "Total"
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
@ -1951,6 +1979,11 @@ msgstr "Estado"
msgid "No booking found."
msgstr "No se ha encontrado ninguna reserva."
#: pkg/payment/public.go:97
msgctxt "order product name"
msgid "Campsite Booking"
msgstr "Reserva de camping"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:412
@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type."
msgstr "La imagen de la diapositiva tiene que ser un medio de tipo imagen."
#: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217
#: pkg/booking/public.go:583
#: pkg/booking/public.go:533
msgid "Email can not be empty."
msgstr "No podéis dejar el correo-e en blanco."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218
#: pkg/booking/public.go:584
#: pkg/booking/public.go:534
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com."
@ -2206,8 +2239,8 @@ msgctxt "header"
msgid "Children (aged 2 to 10)"
msgstr "Niños (de 2 a 10 años)"
#: pkg/campsite/admin.go:275 pkg/booking/public.go:224
#: pkg/booking/public.go:276
#: pkg/campsite/admin.go:275 pkg/booking/public.go:171
#: pkg/booking/public.go:226
msgid "Selected campsite type is not valid."
msgstr "El tipo de alojamiento escogido no es válido."
@ -2363,7 +2396,7 @@ msgstr "No podéis dejar la dirección del enlace en blanco."
msgid "This web address is not valid. It should be like https://domain.com/."
msgstr "Esta dirección web no es válida. Tiene que ser parecido a https://dominio.com/."
#: pkg/company/admin.go:200 pkg/booking/public.go:570
#: pkg/company/admin.go:200 pkg/booking/public.go:520
msgid "Selected country is not valid."
msgstr "El país escogido no es válido."
@ -2383,11 +2416,11 @@ msgstr "No podéis dejar el NIF en blanco."
msgid "This VAT number is not valid."
msgstr "Este NIF no es válido."
#: pkg/company/admin.go:212 pkg/booking/public.go:586
#: pkg/company/admin.go:212 pkg/booking/public.go:536
msgid "Phone can not be empty."
msgstr "No podéis dejar el teléfono en blanco."
#: pkg/company/admin.go:213 pkg/booking/public.go:587
#: pkg/company/admin.go:213 pkg/booking/public.go:537
msgid "This phone number is not valid."
msgstr "Este teléfono no es válido."
@ -2407,7 +2440,7 @@ msgstr "No podéis dejar la provincia en blanco."
msgid "Postal code can not be empty."
msgstr "No podéis dejar el código postal en blanco."
#: pkg/company/admin.go:227 pkg/booking/public.go:579
#: pkg/company/admin.go:227 pkg/booking/public.go:529
msgid "This postal code is not valid."
msgstr "Este código postal no es válido."
@ -2534,127 +2567,135 @@ msgstr "La integración escogida no es válida."
msgid "The merchant key is not valid."
msgstr "Esta clave del comercio no es válida."
#: pkg/booking/public.go:325 pkg/booking/public.go:354
#: pkg/booking/public.go:275 pkg/booking/public.go:304
msgid "Arrival date must be a valid date."
msgstr "La fecha de llegada tiene que ser una fecha válida."
#: pkg/booking/public.go:339 pkg/booking/public.go:361
#: pkg/booking/public.go:289 pkg/booking/public.go:311
msgid "Departure date must be a valid date."
msgstr "La fecha de partida tiene que ser una fecha válida."
#: pkg/booking/public.go:353
#: pkg/booking/public.go:303
msgid "Arrival date can not be empty"
msgstr "No podéis dejar la fecha de llegada en blanco."
#: pkg/booking/public.go:355
#: pkg/booking/public.go:305
#, c-format
msgid "Arrival date must be %s or after."
msgstr "La fecha de llegada tiene que ser igual o posterior a %s."
#: pkg/booking/public.go:356
#: pkg/booking/public.go:306
#, c-format
msgid "Arrival date must be %s or before."
msgstr "La fecha de llegada tiene que ser anterior o igual a %s."
#: pkg/booking/public.go:360
#: pkg/booking/public.go:310
msgid "Departure date can not be empty"
msgstr "No podéis dejar la fecha de partida en blanco."
#: pkg/booking/public.go:362
#: pkg/booking/public.go:312
#, c-format
msgid "Departure date must be %s or after."
msgstr "La fecha de partida tiene que igual o posterior a %s."
#: pkg/booking/public.go:363
#: pkg/booking/public.go:313
#, c-format
msgid "Departure date must be %s or before."
msgstr "La fecha de partida tiene que ser anterior o igual a %s."
#: pkg/booking/public.go:405
#: pkg/booking/public.go:355
#, c-format
msgid "There can be at most %d guests in this accommodation."
msgstr "Solo puede haber como máximo %d invitados en este alojamiento."
#: pkg/booking/public.go:425
#: pkg/booking/public.go:375
msgid "Number of adults can not be empty"
msgstr "No podéis dejar el número de adultos blanco."
#: pkg/booking/public.go:426
#: pkg/booking/public.go:376
msgid "Number of adults must be an integer."
msgstr "El número de adultos tiene que ser entero."
#: pkg/booking/public.go:427
#: pkg/booking/public.go:377
msgid "There must be at least one adult."
msgstr "Tiene que haber como mínimo un adulto."
#: pkg/booking/public.go:430
#: pkg/booking/public.go:380
msgid "Number of teenagers can not be empty"
msgstr "No podéis dejar el número de adolescentes en blanco."
#: pkg/booking/public.go:431
#: pkg/booking/public.go:381
msgid "Number of teenagers must be an integer."
msgstr "El número de adolescentes tiene que ser entero."
#: pkg/booking/public.go:432
#: pkg/booking/public.go:382
msgid "Number of teenagers can not be negative."
msgstr "El número de adolescentes no puede ser negativo."
#: pkg/booking/public.go:435
#: pkg/booking/public.go:385
msgid "Number of children can not be empty"
msgstr "No podéis dejar el número de niños en blanco."
#: pkg/booking/public.go:436
#: pkg/booking/public.go:386
msgid "Number of children must be an integer."
msgstr "El número de niños tiene que ser entero."
#: pkg/booking/public.go:437
#: pkg/booking/public.go:387
msgid "Number of children can not be negative."
msgstr "El número de niños no puede ser negativo."
#: pkg/booking/public.go:440
#: pkg/booking/public.go:390
msgid "Number of dogs can not be empty"
msgstr "No podéis dejar el número de perros en blanco."
#: pkg/booking/public.go:441
#: pkg/booking/public.go:391
msgid "Number of dogs must be an integer."
msgstr "El número de perros tiene que ser entero."
#: pkg/booking/public.go:442
#: pkg/booking/public.go:392
msgid "Number of dogs can not be negative."
msgstr "El número de perros no puede ser negativo."
#: pkg/booking/public.go:513
#: pkg/booking/public.go:463
#, c-format
msgid "%s can not be empty"
msgstr "No podéis dejar %s en blanco."
#: pkg/booking/public.go:514
#: pkg/booking/public.go:464
#, c-format
msgid "%s must be an integer."
msgstr "%s tiene que ser un número entero."
#: pkg/booking/public.go:515
#: pkg/booking/public.go:465
#, c-format
msgid "%s must be %d or greater."
msgstr "%s tiene que ser como mínimo %d."
#: pkg/booking/public.go:516
#: pkg/booking/public.go:466
#, c-format
msgid "%s must be at most %d."
msgstr "%s tiene que ser como máximo %d"
#: pkg/booking/public.go:574
#: pkg/booking/public.go:524
msgid "Full name can not be empty."
msgstr "No podéis dejar el nombre y los apellidos en blanco."
#: pkg/booking/public.go:575
#: pkg/booking/public.go:525
msgid "Full name must have at least one letter."
msgstr "El nombre y los apellidos tienen que tener como mínimo una letra."
#: pkg/booking/public.go:592
#: pkg/booking/public.go:542
msgid "It is mandatory to agree to the reservation conditions."
msgstr "Es obligatorio aceptar las condiciones de reserva."
#~ msgctxt "title"
#~ msgid "Payment"
#~ msgstr "Pago"
#~ msgctxt "action"
#~ msgid "Pay"
#~ msgstr "Pagar"
#~ msgctxt "input"
#~ msgid "Check-in Date"
#~ msgstr "Fecha de entrada"

135
po/fr.po
View File

@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: camper\n"
"Report-Msgid-Bugs-To: jordi@tandem.blog\n"
"POT-Creation-Date: 2024-02-12 05:10+0100\n"
"POT-Creation-Date: 2024-02-12 17:49+0100\n"
"PO-Revision-Date: 2024-02-06 10:05+0100\n"
"Last-Translator: Oriol Carbonell <info@oriolcarbonell.cat>\n"
"Language-Team: French <traduc@traduc.org>\n"
@ -60,28 +60,56 @@ msgid "Zone 1"
msgstr "Zone 1"
#: web/templates/public/payment/success.gohtml:6
#: web/templates/public/payment/success.gohtml:11
msgctxt "title"
msgid "Payment Successful"
msgstr "Paiement réussi"
#: web/templates/public/payment/request.gohtml:6
#: web/templates/public/payment/request.gohtml:11
msgctxt "title"
msgid "Payment"
msgstr "Paiement"
#: web/templates/public/payment/success.gohtml:12
msgid "We have received the payment. Thank you."
msgstr "Nous avons reçu le paiement. Merci."
#: web/templates/public/payment/request.gohtml:23
#: web/templates/public/payment/request.gohtml:6
msgctxt "title"
msgid "Booking Payment"
msgstr "Paiement de la réservation"
#: web/templates/public/payment/request.gohtml:18
msgid "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys."
msgstr "Merci pour votre réservation. Veuillez cliquer sur le bouton ci-dessous pour payer avec une carte de crédit via Servired/Redsys."
#: web/templates/public/payment/request.gohtml:27
msgctxt "action"
msgid "Pay"
msgstr "Payer"
msgid "Pay with credit card"
msgstr "Payez avec une carte de crédit"
#: web/templates/public/payment/request.gohtml:30
msgid "Please, wait until we redirect you to the payment page."
msgstr "Veuillez attendre que nous vous redirigeons vers la page de paiement."
#: web/templates/public/payment/failure.gohtml:6
#: web/templates/public/payment/failure.gohtml:11
msgctxt "title"
msgid "Payment Failed"
msgstr "Le paiement a échoué"
#: web/templates/public/payment/failure.gohtml:12
msgid "We could not process the payment. Please, contact us for support."
msgstr "Nous navons pas pu traiter le paiement. Sil vous plaît, contactez-nous pour obtenir de laide."
#: web/templates/public/payment/details.gohtml:4
msgctxt "title"
msgid "Order Number"
msgstr "Numéro de commande"
#: web/templates/public/payment/details.gohtml:8
msgctxt "title"
msgid "Date"
msgstr "Date"
#: web/templates/public/payment/details.gohtml:12
msgctxt "title"
msgid "Total"
msgstr "Totale"
#: web/templates/public/services.gohtml:7
#: web/templates/public/services.gohtml:16
#: web/templates/public/layout.gohtml:67 web/templates/public/layout.gohtml:95
@ -1951,6 +1979,11 @@ msgstr "Statut"
msgid "No booking found."
msgstr "Aucune réservation trouvée."
#: pkg/payment/public.go:97
msgctxt "order product name"
msgid "Campsite Booking"
msgstr "Réservation camping"
#: pkg/legal/admin.go:258 pkg/app/user.go:249 pkg/campsite/types/option.go:365
#: pkg/campsite/types/feature.go:272 pkg/campsite/types/admin.go:577
#: pkg/campsite/feature.go:269 pkg/season/admin.go:412
@ -1990,12 +2023,12 @@ msgid "Slide image must be an image media type."
msgstr "Limage de la diapositive doit être de type média dimage."
#: pkg/app/login.go:56 pkg/app/user.go:246 pkg/company/admin.go:217
#: pkg/booking/public.go:583
#: pkg/booking/public.go:533
msgid "Email can not be empty."
msgstr "Le-mail ne peut pas être vide."
#: pkg/app/login.go:57 pkg/app/user.go:247 pkg/company/admin.go:218
#: pkg/booking/public.go:584
#: pkg/booking/public.go:534
msgid "This email is not valid. It should be like name@domain.com."
msgstr "Cette adresse e-mail nest pas valide. Il devrait en être name@domain.com."
@ -2206,8 +2239,8 @@ msgctxt "header"
msgid "Children (aged 2 to 10)"
msgstr "Enfants (de 2 à 10 anys)"
#: pkg/campsite/admin.go:275 pkg/booking/public.go:224
#: pkg/booking/public.go:276
#: pkg/campsite/admin.go:275 pkg/booking/public.go:171
#: pkg/booking/public.go:226
msgid "Selected campsite type is not valid."
msgstr "Le type demplacement sélectionné nest pas valide."
@ -2363,7 +2396,7 @@ msgstr "Laddresse du lien ne peut pas être vide."
msgid "This web address is not valid. It should be like https://domain.com/."
msgstr "Cette adresse web nest pas valide. Il devrait en être https://domain.com/."
#: pkg/company/admin.go:200 pkg/booking/public.go:570
#: pkg/company/admin.go:200 pkg/booking/public.go:520
msgid "Selected country is not valid."
msgstr "Le pays sélectionné nest pas valide."
@ -2383,11 +2416,11 @@ msgstr "Le numéro de TVA ne peut pas être vide."
msgid "This VAT number is not valid."
msgstr "Ce numéro de TVA nest pas valide."
#: pkg/company/admin.go:212 pkg/booking/public.go:586
#: pkg/company/admin.go:212 pkg/booking/public.go:536
msgid "Phone can not be empty."
msgstr "Le téléphone ne peut pas être vide."
#: pkg/company/admin.go:213 pkg/booking/public.go:587
#: pkg/company/admin.go:213 pkg/booking/public.go:537
msgid "This phone number is not valid."
msgstr "Ce numéro de téléphone nest pas valide."
@ -2407,7 +2440,7 @@ msgstr "La province ne peut pas être vide."
msgid "Postal code can not be empty."
msgstr "Le code postal ne peut pas être vide."
#: pkg/company/admin.go:227 pkg/booking/public.go:579
#: pkg/company/admin.go:227 pkg/booking/public.go:529
msgid "This postal code is not valid."
msgstr "Ce code postal nest pas valide."
@ -2534,127 +2567,135 @@ msgstr "Lintégration sélectionnée nest pas valide."
msgid "The merchant key is not valid."
msgstr "La clé marchand nest pas valide."
#: pkg/booking/public.go:325 pkg/booking/public.go:354
#: pkg/booking/public.go:275 pkg/booking/public.go:304
msgid "Arrival date must be a valid date."
msgstr "La date darrivée doit être une date valide."
#: pkg/booking/public.go:339 pkg/booking/public.go:361
#: pkg/booking/public.go:289 pkg/booking/public.go:311
msgid "Departure date must be a valid date."
msgstr "La date de départ doit être une date valide."
#: pkg/booking/public.go:353
#: pkg/booking/public.go:303
msgid "Arrival date can not be empty"
msgstr "La date darrivée ne peut pas être vide"
#: pkg/booking/public.go:355
#: pkg/booking/public.go:305
#, c-format
msgid "Arrival date must be %s or after."
msgstr "La date darrivée doit être égale ou postérieure à %s."
#: pkg/booking/public.go:356
#: pkg/booking/public.go:306
#, c-format
msgid "Arrival date must be %s or before."
msgstr "La date darrivée doit être antérieure ou égale à %s."
#: pkg/booking/public.go:360
#: pkg/booking/public.go:310
msgid "Departure date can not be empty"
msgstr "La date de départ ne peut pas être vide"
#: pkg/booking/public.go:362
#: pkg/booking/public.go:312
#, c-format
msgid "Departure date must be %s or after."
msgstr "La date de départ doit être égale ou postérieure à %s."
#: pkg/booking/public.go:363
#: pkg/booking/public.go:313
#, c-format
msgid "Departure date must be %s or before."
msgstr "La date de départ doit être antérieure ou égale à %s."
#: pkg/booking/public.go:405
#: pkg/booking/public.go:355
#, c-format
msgid "There can be at most %d guests in this accommodation."
msgstr "Il peut y avoir au plus %d invités dans cet hébergement."
#: pkg/booking/public.go:425
#: pkg/booking/public.go:375
msgid "Number of adults can not be empty"
msgstr "Le nombre dadultes ne peut pas être vide."
#: pkg/booking/public.go:426
#: pkg/booking/public.go:376
msgid "Number of adults must be an integer."
msgstr "Le nombre dadultes doit être un entier."
#: pkg/booking/public.go:427
#: pkg/booking/public.go:377
msgid "There must be at least one adult."
msgstr "Il doit y avoir au moins un adulte."
#: pkg/booking/public.go:430
#: pkg/booking/public.go:380
msgid "Number of teenagers can not be empty"
msgstr "Le nombre dadolescents ne peut pas être vide."
#: pkg/booking/public.go:431
#: pkg/booking/public.go:381
msgid "Number of teenagers must be an integer."
msgstr "Le nombre dadolescents doit être un entier."
#: pkg/booking/public.go:432
#: pkg/booking/public.go:382
msgid "Number of teenagers can not be negative."
msgstr "Le nombre dadolescents ne peut pas être négatif."
#: pkg/booking/public.go:435
#: pkg/booking/public.go:385
msgid "Number of children can not be empty"
msgstr "Le nombre denfants ne peut pas être vide."
#: pkg/booking/public.go:436
#: pkg/booking/public.go:386
msgid "Number of children must be an integer."
msgstr "Le nombre denfants doit être un entier."
#: pkg/booking/public.go:437
#: pkg/booking/public.go:387
msgid "Number of children can not be negative."
msgstr "Le nombre denfants ne peut pas être négatif."
#: pkg/booking/public.go:440
#: pkg/booking/public.go:390
msgid "Number of dogs can not be empty"
msgstr "Le nombre de chiens ne peut pas être vide."
#: pkg/booking/public.go:441
#: pkg/booking/public.go:391
msgid "Number of dogs must be an integer."
msgstr "Le nombre de chiens nuits être un entier."
#: pkg/booking/public.go:442
#: pkg/booking/public.go:392
msgid "Number of dogs can not be negative."
msgstr "Le nombre de chiens ne peut pas être négatif."
#: pkg/booking/public.go:513
#: pkg/booking/public.go:463
#, c-format
msgid "%s can not be empty"
msgstr "%s ne peut pas être vide"
#: pkg/booking/public.go:514
#: pkg/booking/public.go:464
#, c-format
msgid "%s must be an integer."
msgstr "%s doit être un entier."
#: pkg/booking/public.go:515
#: pkg/booking/public.go:465
#, c-format
msgid "%s must be %d or greater."
msgstr "%s doit être %d ou plus."
#: pkg/booking/public.go:516
#: pkg/booking/public.go:466
#, c-format
msgid "%s must be at most %d."
msgstr "%s doit être tout au plus %d."
#: pkg/booking/public.go:574
#: pkg/booking/public.go:524
msgid "Full name can not be empty."
msgstr "Le nom complet ne peut pas être vide."
#: pkg/booking/public.go:575
#: pkg/booking/public.go:525
msgid "Full name must have at least one letter."
msgstr "Le nom complet doit comporter au moins une lettre."
#: pkg/booking/public.go:592
#: pkg/booking/public.go:542
msgid "It is mandatory to agree to the reservation conditions."
msgstr "Il est obligatoire daccepter les conditions de réservation."
#~ msgctxt "title"
#~ msgid "Payment"
#~ msgstr "Paiement"
#~ msgctxt "action"
#~ msgid "Pay"
#~ msgstr "Payer"
#~ msgctxt "input"
#~ msgid "Check-in Date"
#~ msgstr "Date d'arrivée"

7
revert/ready_payment.sql Normal file
View File

@ -0,0 +1,7 @@
-- Revert camper:ready_payment from pg
begin;
drop function if exists camper.ready_payment(uuid, text, text, text, text, camper.country_code, camper.email, text, text, boolean);
commit;

View File

@ -247,3 +247,4 @@ payment [roles schema_camper company campsite_type payment_status] 2024-02-11T21
payment_customer [roles schema_camper payment country country_code extension_pg_libphonenumber] 2024-02-12T00:10:20Z jordi fita mas <jordi@tandem.blog> # Add relation of payment customer
payment_option [roles schema_camper payment campsite_type_option] 2024-02-12T00:58:07Z jordi fita mas <jordi@tandem.blog> # Add relation of payment for campsite type options
draft_payment [roles schema_camper season_calendar season campsite_type campsite_type_pet_cost campsite_type_cost campsite_type_option_cost campsite_type_option payment payment_option] 2024-02-12T01:31:52Z jordi fita mas <jordi@tandem.blog> # Add function to create a payment draft
ready_payment [roles schema_camper payment payment_customer country_code email extension_pg_libphonenumber] 2024-02-12T12:57:24Z jordi fita mas <jordi@tandem.blog> # Add function to ready a draft payment

View File

@ -115,9 +115,9 @@ select lives_ok(
);
select bag_eq(
$$ select company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, created_at, updated_at from payment $$,
$$ values (2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', '2024-01-01 01:01:01', current_timestamp)
, (2, 14, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', current_timestamp, current_timestamp)
$$ select company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at from payment $$,
$$ values (2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'draft', '2024-01-01 01:01:01', current_timestamp)
, (2, 14, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'draft', current_timestamp, current_timestamp)
$$,
'Should have added and updated payments'
);

View File

@ -5,13 +5,13 @@ reset client_min_messages;
begin;
select plan(47);
select plan(53);
set search_path to camper, public;
select has_table('payment_customer');
select has_pk('payment_customer');
select table_privs_are('payment_customer', 'guest', array['SELECT', 'INSERT']);
select table_privs_are('payment_customer', 'guest', array['SELECT', 'INSERT', 'UPDATE']);
select table_privs_are('payment_customer', 'employee', array['SELECT', 'INSERT', 'UPDATE']);
select table_privs_are('payment_customer', 'admin', array['SELECT', 'INSERT', 'UPDATE', 'DELETE']);
select table_privs_are('payment_customer', 'authenticator', array[]::text[]);
@ -66,6 +66,13 @@ select col_type_is('payment_customer', 'acsi_card', 'boolean');
select col_not_null('payment_customer', 'acsi_card');
select col_hasnt_default('payment_customer', 'acsi_card');
select has_column('payment_customer', 'lang_tag');
select col_is_fk('payment_customer', 'lang_tag');
select fk_ok('payment_customer', 'lang_tag', 'language', 'lang_tag');
select col_type_is('payment_customer', 'lang_tag', 'text');
select col_not_null('payment_customer', 'lang_tag');
select col_hasnt_default('payment_customer', 'lang_tag');
select *
from finish();

101
test/ready_payment.sql Normal file
View File

@ -0,0 +1,101 @@
-- Test ready_payment
set client_min_messages to warning;
create extension if not exists pgtap;
reset client_min_messages;
begin;
select plan(15);
set search_path to camper, public;
select has_function('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean']);
select function_lang_is('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'plpgsql');
select function_returns('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'integer');
select isnt_definer('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean']);
select volatility_is('camper', 'ready_payment', array['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'volatile');
select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'guest', array['EXECUTE']);
select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'employee', array['EXECUTE']);
select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'admin', array['EXECUTE']);
select function_privs_are('camper', 'ready_payment', array ['uuid', 'text', 'text', 'text', 'text', 'country_code', 'email', 'text', 'text', 'boolean'], 'authenticator', array[]::text[]);
set client_min_messages to warning;
truncate payment_customer cascade;
truncate payment cascade;
truncate campsite_type cascade;
truncate media cascade;
truncate media_content cascade;
truncate company cascade;
reset client_min_messages;
insert into company (company_id, business_name, vatin, trade_name, phone, email, web, address, city, province, postal_code, rtc_number, tourist_tax, country_code, currency_code, default_lang_tag)
values (2, 'Company 2', 'XX123', '', '555-555-555', 'a@a', '', '', '', '', '', '', 350, 'ES', 'EUR', 'ca')
;
insert into media_content (media_type, bytes)
values ('image/x-xpixmap', 'static char *s[]={"1 1 1 1","a c #ffffff","a"};')
;
insert into media (media_id, company_id, original_filename, content_hash)
values (10, 2, 'cover2.xpm', sha256('static char *s[]={"1 1 1 1","a c #ffffff","a"};'))
;
insert into campsite_type (campsite_type_id, slug, company_id, name, media_id, max_campers, bookable_nights, overflow_allowed)
values (12, 'c1b6f4fc-32c1-4cd5-b796-0c5059152a52', 2, 'Plots', 10, 6, '[1, 7]', true)
;
insert into payment (payment_id, slug, company_id, campsite_type_id, arrival_date, departure_date, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at)
values (22, '4ef35e2f-ef98-42d6-a724-913bd761ca8c', 2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'draft', '2024-01-01 01:01:01', '2024-01-01')
, (24, '6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 2, 12, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'draft', current_timestamp, current_timestamp)
;
insert into payment_customer (payment_id, full_name, address, postal_code, city, country_code, email, phone, acsi_card, lang_tag)
values (24, '', '', '', '', 'FR', 'a@a.com', '555-555-555', true, 'es')
;
select is(
ready_payment('4ef35e2f-ef98-42d6-a724-913bd761ca8c', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló dEmpúries', 'ES', 'juli@verd.cat', '972486 160', 'ca', true),
22,
'Should be able to ready the first draft payment'
);
select is(
ready_payment('6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 'Pere Gil', 'Gutenbergstr. 9-13', '82178', 'Puchheim', 'DE', 'pere@gil.de', '8980902-0', 'en', false),
24,
'Should be able to ready the second draft payment, and update customer details'
);
select throws_ok(
$$ select ready_payment('7ba7f0a5-d73d-4d6e-a9c4-3b88f2b2a357', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló dEmpúries', 'ES', 'juli@verd.cat', '972 486 160', 'ca', true) $$,
'23514', 'insert or update on table "payment" violates check constraint "payment_is_draft"',
'Should not be able to ready an inexistent payment'
);
select throws_ok(
$$ select ready_payment('4ef35e2f-ef98-42d6-a724-913bd761ca8c', 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló dEmpúries', 'ES', 'juli@verd.cat', '972 486 160', 'ca', true) $$,
'23514', 'insert or update on table "payment" violates check constraint "payment_is_draft"',
'Should not be able to ready an already processed payment'
);
select bag_eq(
$$ select payment_id, slug::text, company_id, campsite_type_id, arrival_date::text, departure_date::text, subtotal_nights, number_adults, subtotal_adults, number_teenagers, subtotal_teenagers, number_children, subtotal_children, number_dogs, subtotal_dogs, subtotal_tourist_tax, total, zone_preferences, payment_status, created_at, updated_at from payment $$,
$$ values (22, '4ef35e2f-ef98-42d6-a724-913bd761ca8c', 2, 12, '2024-08-28', '2024-09-04', 3200, 2, 10420, 4, 20840, 6, 25080, 3, 2450, 4900, 79160, 'pref I before E', 'pending', '2024-01-01 01:01:01', current_timestamp)
, (24, '6d1b8e4c-c3c6-4fe4-92c1-2cbf94526693', 2, 12, '2024-08-29', '2024-09-03', 71000, 1, 0, 2, 0, 3, 0, 0, 0, 1750, 72750, '', 'pending', current_timestamp, current_timestamp)
$$,
'Should have updated payments'
);
select bag_eq(
$$ select payment_id, full_name, address, postal_code, city, country_code::text, email::text, phone::text, acsi_card, lang_tag from payment_customer $$,
$$ values (22, 'Juli Verd', 'C/ Fals, 123', '17486', 'Castelló dEmpúries', 'ES', 'juli@verd.cat', '+34 972 48 61 60', true, 'ca')
, (24, 'Pere Gil', 'Gutenbergstr. 9-13', '82178', 'Puchheim', 'DE', 'pere@gil.de', '+49 89 809020', false, 'en')
$$,
'Should have added and updated customer details'
);
select *
from finish();
rollback;

View File

@ -11,6 +11,7 @@ select payment_id
, email
, phone
, acsi_card
, lang_tag
from camper.payment_customer
where false;

7
verify/ready_payment.sql Normal file
View File

@ -0,0 +1,7 @@
-- Verify camper:ready_payment on pg
begin;
select has_function_privilege('camper.ready_payment(uuid, text, text, text, text, camper.country_code, camper.email, text, text, boolean)', 'execute');
rollback;

View File

@ -140,6 +140,10 @@ p + p, dl + p {
margin-top: 1.5em;
}
dialog:modal {
margin: auto;
}
h2 {
font-size: 4.2rem;
font-weight: 400;
@ -1526,6 +1530,16 @@ input[type="checkbox"]:focus {
width: 4.8rem;
}
dl.payment-details div {
flex-basis: unset;
min-height: unset;
padding: unset;
}
dl.payment-details dt {
padding: unset;
}
body > footer {
display: flex;
flex-direction: column;
@ -1812,7 +1826,6 @@ dt {
z-index: 3;
}
.services_icon::before {
content: '';
background: var(--accent-2);
@ -1825,17 +1838,41 @@ dt {
z-index: 1;
}
/*
.services_icon:hover::before {
background: var(--accent-2);
}
*/
.services_icon a {
color: var(--accent);
text-decoration: var(--accent) wavy underline;
}
/*<editor-fold desc="redirect dialog">*/
dialog.redirect:modal {
display: flex;
gap: 1rem;
}
dialog.redirect::before {
content: "";
display: inline-block;
width: 2.4rem;
aspect-ratio: 1;
border: 3px solid var(--contrast);
border-bottom-color: transparent;
border-radius: 50%;
animation: rotation linear 1s infinite;
}
@keyframes rotation {
from {
transform: rotate(0);
}
to {
transform: rotate(360deg);
}
}
/*</editor-fold>*/
[x-cloak] {
display: none !important;
}

View File

@ -0,0 +1,15 @@
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.Payment*/ -}}
<dl class="payment-details">
<div>
<dt>{{( pgettext "Order Number" "title" )}}</dt>
<dd>{{ .OrderNumber }}</dd>
</div>
<div>
<dt>{{( pgettext "Date" "title" )}}</dt>
<dd>{{ .CreateTime | formatDate }}</dd>
</div>
<div>
<dt>{{( pgettext "Total" "title" )}}</dt>
<dd>{{ .Total | formatPrice }}</dd>
</div>
</dl>

View File

@ -7,6 +7,8 @@
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}}
<h2>{{( pgettext "Payment Failed" "title" )}}</h2>
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.failedPaymentPage*/ -}}
<h2>{{ template "title" . }}</h2>
<p>{{( gettext "We could not process the payment. Please, contact us for support." )}}</p>
{{ template "details.gohtml" .Payment }}
{{- end }}

View File

@ -3,24 +3,31 @@
SPDX-License-Identifier: AGPL-3.0-only
-->
{{ define "title" -}}
{{( pgettext "Payment" "title" )}}
{{( pgettext "Booking Payment" "title" )}}
{{- end }}
{{ define "head" -}}
{{ template "alpineScript" }}
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.paymentPage*/ -}}
<h2>{{( pgettext "Payment" "title" )}}</h2>
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.paymentPage*/ -}}
<h2>{{ template "title" . }}</h2>
{{ template "details.gohtml" .Payment }}
{{ with .Request -}}
<form
{{ if eq $.Environment "live" -}}
action="https://sis.redsys.es/sis/realizarPago"
{{- else -}}
action="https://sis-t.redsys.es:25443/sis/realizarPago"
{{- end }}
method="post">
<p>{{( gettext "Thank you for your booking. Please, click the button below to pay with a credit card via Servired/Redsys.")}}</p>
<form id="payment" method="post" x-init="$el.submit()"
{{- if eq $.Environment "live" }} action="https://sis.redsys.es/sis/realizarPago"
{{- else }} action="https://sis-t.redsys.es:25443/sis/realizarPago"
{{- end -}}
>
<input type="hidden" name="Ds_MerchantParameters" value="{{ .MerchantParameters }}"/>
<input type="hidden" name="Ds_Signature" value="{{ .Signature }}"/>
<input type="hidden" name="Ds_SignatureVersion" value="{{ .SignatureVersion }}"/>
<button type="submit">{{( pgettext "Pay" "action" )}}</button>
<button type="submit">{{( pgettext "Pay with credit card" "action" )}}</button>
</form>
<dialog class="redirect" x-init="$el.showModal()">
<p>{{( gettext "Please, wait until we redirect you to the payment page.")}}</p>
</dialog>
{{- end }}
{{- end }}

View File

@ -7,6 +7,8 @@
{{- end }}
{{ define "content" -}}
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/booking.failedPaymentPage*/ -}}
<h2>{{( pgettext "Payment Successful" "title" )}}</h2>
{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/payment.successfulPaymentPage*/ -}}
<h2>{{ template "title" . }}</h2>
<p>{{( gettext "We have received the payment. Thank you." )}}</p>
{{ template "details.gohtml" .Payment }}
{{- end }}