taler-merchant-demos

Python-based Frontends for the Demonstration Web site
Log | Files | Refs | README | LICENSE

donations.go (7291B)


      1 package web
      2 
      3 import (
      4 	"net/http"
      5 	"net/url"
      6 	"strconv"
      7 	"strings"
      8 	"time"
      9 
     10 	"git.taler.net/taler-merchant-demos/internal/backend"
     11 )
     12 
     13 type donationsIndexContent struct{ Currency string }
     14 
     15 type checkoutContent struct {
     16 	Currency string
     17 	Receiver string
     18 	Amount   string
     19 	Donor    string
     20 }
     21 
     22 type fulfillmentContent struct {
     23 	Receiver   string
     24 	Amount     string
     25 	Donor      string
     26 	OrderID    string
     27 	RequestURL string
     28 }
     29 
     30 type providerNotSupportedContent struct {
     31 	BackURL        string
     32 	BackToCheckout bool
     33 }
     34 
     35 func (a *App) registerDonationRoutes() {
     36 	a.handleGet("/{lang}/{$}", a.donationsIndex)
     37 	a.handleGet("/{lang}/checkout", a.donationCheckout)
     38 	a.handleGet("/{lang}/provider-not-supported", a.donationProviderNotSupported)
     39 	a.handleGet("/{lang}/donate", a.donate)
     40 	a.handleGet("/{lang}/donation/{receiver}", a.donationFulfillment)
     41 }
     42 
     43 func (a *App) donationLanguage(w http.ResponseWriter, r *http.Request) (string, bool) {
     44 	lang := r.PathValue("lang")
     45 	if !validLocale(lang) {
     46 		a.renderError(w, r, http.StatusNotFound, "en", "Page not found", nil)
     47 		return "", false
     48 	}
     49 	return lang, true
     50 }
     51 
     52 func (a *App) donationsIndex(w http.ResponseWriter, r *http.Request) {
     53 	lang, ok := a.donationLanguage(w, r)
     54 	if !ok {
     55 		return
     56 	}
     57 	a.render(w, "donations-index", a.makePage(r, lang, "GNU Taler Demo: Donations", donationsIndexContent{a.opts.Currency}), http.StatusOK)
     58 }
     59 
     60 func (a *App) donationProviderNotSupported(w http.ResponseWriter, r *http.Request) {
     61 	lang, ok := a.donationLanguage(w, r)
     62 	if !ok {
     63 		return
     64 	}
     65 	prefix := forwardedPrefix(r) + "/" + url.PathEscape(lang)
     66 	query := r.URL.Query()
     67 	receiver := strings.TrimSpace(query.Get("donation_receiver"))
     68 	amount := strings.TrimSpace(query.Get("donation_amount"))
     69 	donor := strings.TrimSpace(query.Get("donation_donor"))
     70 	content := providerNotSupportedContent{BackURL: prefix + "/"}
     71 	if receiver != "" && amount != "" && donor != "" {
     72 		values := url.Values{
     73 			"donation_receiver": {receiver},
     74 			"donation_amount":   {amount},
     75 			"donation_donor":    {donor},
     76 		}
     77 		content.BackURL = prefix + "/checkout?" + values.Encode()
     78 		content.BackToCheckout = true
     79 	}
     80 	a.render(w, "donations-provider", a.makePage(r, lang, "GNU Taler Demo: Donations", content), http.StatusOK)
     81 }
     82 
     83 func (a *App) requiredQuery(w http.ResponseWriter, r *http.Request, lang, name string) (string, bool) {
     84 	value := strings.TrimSpace(r.URL.Query().Get(name))
     85 	if value == "" {
     86 		a.renderErrorf(w, r, http.StatusBadRequest, lang, "Parameter {name} is required.", nil, "name", name)
     87 		return "", false
     88 	}
     89 	return value, true
     90 }
     91 
     92 func (a *App) donationCheckout(w http.ResponseWriter, r *http.Request) {
     93 	lang, ok := a.donationLanguage(w, r)
     94 	if !ok {
     95 		return
     96 	}
     97 	receiver, ok := a.requiredQuery(w, r, lang, "donation_receiver")
     98 	if !ok {
     99 		return
    100 	}
    101 	amount, ok := a.requiredQuery(w, r, lang, "donation_amount")
    102 	if !ok {
    103 		return
    104 	}
    105 	donor, ok := a.requiredQuery(w, r, lang, "donation_donor")
    106 	if !ok {
    107 		return
    108 	}
    109 	content := checkoutContent{Currency: a.opts.Currency, Receiver: receiver, Amount: amount, Donor: donor}
    110 	a.render(w, "donations-checkout", a.makePage(r, lang, "GNU Taler Demo: Donations checkout", content), http.StatusOK)
    111 }
    112 
    113 func (a *App) donate(w http.ResponseWriter, r *http.Request) {
    114 	lang, ok := a.donationLanguage(w, r)
    115 	if !ok {
    116 		return
    117 	}
    118 	receiver, ok := a.requiredQuery(w, r, lang, "donation_receiver")
    119 	if !ok {
    120 		return
    121 	}
    122 	client := a.opts.DonationBackends[receiver]
    123 	if client == nil {
    124 		a.renderError(w, r, http.StatusBadRequest, lang, "Unknown donation receiver", nil)
    125 		return
    126 	}
    127 	amount, ok := a.requiredQuery(w, r, lang, "donation_amount")
    128 	if !ok {
    129 		return
    130 	}
    131 	donor, ok := a.requiredQuery(w, r, lang, "donation_donor")
    132 	if !ok {
    133 		return
    134 	}
    135 	paymentSystem, ok := a.requiredQuery(w, r, lang, "payment_system")
    136 	if !ok {
    137 		return
    138 	}
    139 	if paymentSystem != "taler" {
    140 		query := url.Values{
    141 			"donation_receiver": {receiver},
    142 			"donation_amount":   {amount},
    143 			"donation_donor":    {donor},
    144 		}
    145 		target := forwardedPrefix(r) + "/" + url.PathEscape(lang) + "/provider-not-supported?" + query.Encode()
    146 		http.Redirect(w, r, target, http.StatusFound)
    147 		return
    148 	}
    149 	fulfillmentURL := externalOrigin(r) + forwardedPrefix(r) + "/" + url.PathEscape(lang) + "/donation/" + url.PathEscape(receiver) + "?timestamp=" + strconv.FormatInt(time.Now().UnixNano(), 10) + "&order_id=${ORDER_ID}"
    150 	order := backend.Order{
    151 		Extra:          backend.OrderExtra{Donor: donor, Receiver: receiver, Amount: amount},
    152 		FulfillmentURL: fulfillmentURL,
    153 		// Translators: Merchant order summary shown in the wallet; {receiver} is the project receiving the donation.
    154 		Summary:              formatNamed(a.catalogs.translate(lang, "Donation to {receiver}"), "receiver", receiver),
    155 		PayDeadline:          backend.Timestamp{Seconds: time.Now().Add(10 * time.Minute).Unix()},
    156 		WireTransferDeadline: backend.Timestamp{Seconds: time.Now().Add(30 * time.Minute).Unix()},
    157 		MinimumAge:           16,
    158 	}
    159 	if a.opts.DonauURL == "" {
    160 		order.Amount = amount
    161 	} else {
    162 		order.Version = 1
    163 		// Translators: Merchant order summary shown in the wallet; {receiver} is the project receiving the donation.
    164 		order.Summary = formatNamed(a.catalogs.translate(lang, "Donation to {receiver} (with receipt)"), "receiver", receiver)
    165 		order.Choices = []backend.OrderChoice{{
    166 			Amount:  amount,
    167 			Outputs: []backend.OrderOutput{{Type: "tax-receipt"}},
    168 		}}
    169 	}
    170 	var response backend.PostOrderResponse
    171 	err := client.Post(r.Context(), "private/orders", backend.PostOrderRequest{Order: order}, &response)
    172 	if err != nil {
    173 		a.renderError(w, r, http.StatusBadGateway, lang, "Backend could not create the order", err)
    174 		return
    175 	}
    176 	createdOrderID := response.OrderID
    177 	if createdOrderID == "" {
    178 		a.renderError(w, r, http.StatusBadGateway, lang, "Backend response did not contain an order ID", nil)
    179 		return
    180 	}
    181 	target := forwardedPrefix(r) + "/" + url.PathEscape(lang) + "/donation/" + url.PathEscape(receiver) + "?order_id=" + url.QueryEscape(createdOrderID)
    182 	http.Redirect(w, r, target, http.StatusFound)
    183 }
    184 
    185 func (a *App) donationFulfillment(w http.ResponseWriter, r *http.Request) {
    186 	lang, ok := a.donationLanguage(w, r)
    187 	if !ok {
    188 		return
    189 	}
    190 	receiver := r.PathValue("receiver")
    191 	client := a.opts.DonationBackends[receiver]
    192 	if client == nil {
    193 		a.renderError(w, r, http.StatusBadRequest, lang, "Unknown donation receiver", nil)
    194 		return
    195 	}
    196 	requestedOrderID, ok := a.requiredQuery(w, r, lang, "order_id")
    197 	if !ok {
    198 		return
    199 	}
    200 	var payment backend.OrderStatusResponse
    201 	if err := client.Get(r.Context(), "private/orders/"+url.PathEscape(requestedOrderID), nil, &payment); err != nil {
    202 		a.renderError(w, r, http.StatusBadGateway, lang, "Backend request failed", err)
    203 		return
    204 	}
    205 	if payment.OrderStatus != "paid" {
    206 		target := payment.OrderStatusURL
    207 		if target == "" {
    208 			a.renderError(w, r, http.StatusBadGateway, lang, "Backend response did not contain an order status URL", nil)
    209 			return
    210 		}
    211 		http.Redirect(w, r, target, http.StatusFound)
    212 		return
    213 	}
    214 	extra := payment.ContractTerms.Extra
    215 	content := fulfillmentContent{
    216 		Receiver: extra.Receiver, Amount: extra.Amount,
    217 		Donor: extra.Donor, OrderID: requestedOrderID,
    218 		RequestURL: externalURLWithQuery(r),
    219 	}
    220 	a.render(w, "donations-fulfillment", a.makePage(r, lang, "GNU Taler Demo: Donations", content), http.StatusOK)
    221 }