Rename config vars. Add widget holder template and endpoint to serve it. Add dockerfile
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
||||
"payouts/internal/api/payout"
|
||||
"payouts/internal/api/user"
|
||||
"payouts/internal/api/version"
|
||||
"payouts/internal/api/widget"
|
||||
appConfig "payouts/internal/config"
|
||||
"payouts/internal/service/monitoring"
|
||||
)
|
||||
@@ -25,6 +26,7 @@ var Module = fx.Options(
|
||||
health.Module,
|
||||
payout.Module,
|
||||
version.Module,
|
||||
widget.Module,
|
||||
monitoring.Module,
|
||||
|
||||
fx.Invoke(RegisterRoutes),
|
||||
@@ -41,8 +43,9 @@ type Params struct {
|
||||
|
||||
PayoutHandler payout.Handler
|
||||
UserHandler user.Handler
|
||||
Version version.Handler
|
||||
HealthHandler health.Handler
|
||||
Version version.Handler
|
||||
Widget widget.Handler
|
||||
|
||||
Metrics monitoring.Metrics
|
||||
}
|
||||
@@ -77,6 +80,9 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
|
||||
payoutRouter.HandleFunc(payout.CreateRoute, p.PayoutHandler.PayoutCreate).Methods(http.MethodPost)
|
||||
payoutRouter.HandleFunc(payout.CallbackRoute, p.PayoutHandler.PayoutCallback).Methods(http.MethodPost)
|
||||
|
||||
// Widget endpoint
|
||||
router.HandleFunc(widget.WidgetPage, p.Widget.WidgetHandler).Methods(http.MethodGet)
|
||||
|
||||
// collect api metrics
|
||||
apiRouter.Use(p.Metrics.GetMiddleware())
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentCreate implements [Handler].
|
||||
// PayoutCreate implements [Handler].
|
||||
func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
|
||||
@@ -203,7 +203,7 @@ func (p *payoutHandler) delayedPayoutUpdate(ctx context.Context, payoutData *yoo
|
||||
}
|
||||
}
|
||||
|
||||
// PaymentCallback implements [Handler].
|
||||
// PayoutCallback implements [Handler].
|
||||
func (p *payoutHandler) PayoutCallback(w http.ResponseWriter, r *http.Request) {
|
||||
// todo: check also the X-real-ip and/or X-Forwarded-For
|
||||
if p.yookassaConf.CheckAllowedCallbackAddress && !p.checkAllowedIpCallback(r.RemoteAddr) {
|
||||
|
||||
9
internal/api/widget/module.go
Normal file
9
internal/api/widget/module.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var Module = fx.Options(
|
||||
fx.Provide(NewWidgetHandler),
|
||||
)
|
||||
55
internal/api/widget/widget_handler.go
Normal file
55
internal/api/widget/widget_handler.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package widget
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/fx"
|
||||
|
||||
"payouts/internal/service/yookassa"
|
||||
yookassaConf "payouts/internal/service/yookassa/config"
|
||||
"payouts/internal/templates"
|
||||
)
|
||||
|
||||
const WidgetPage = "/payout/widget"
|
||||
|
||||
type Handler interface {
|
||||
WidgetHandler(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
type widgetHandler struct {
|
||||
template *template.Template
|
||||
config yookassaConf.YooKassa
|
||||
}
|
||||
|
||||
// Params represents the module input params
|
||||
type Params struct {
|
||||
fx.In
|
||||
|
||||
YookassaService yookassa.Service
|
||||
}
|
||||
|
||||
func NewWidgetHandler(p Params) (Handler, error) {
|
||||
return &widgetHandler{
|
||||
template: templates.Templates,
|
||||
config: p.YookassaService.GetConfig(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WidgetHandler renders the payouts widget page
|
||||
func (h *widgetHandler) WidgetHandler(w http.ResponseWriter, r *http.Request) {
|
||||
data := struct {
|
||||
ApiPayoutKey string
|
||||
WidgetVersion string
|
||||
}{
|
||||
ApiPayoutKey: h.config.ApiPayoutKey,
|
||||
WidgetVersion: h.config.WidgetVersion,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
err := h.template.ExecuteTemplate(w, "payouts-widget.html", data)
|
||||
if err != nil {
|
||||
http.Error(w, "internal error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ type PayoutType int64
|
||||
const (
|
||||
TypeSBP PayoutType = iota
|
||||
TypeYooMoney
|
||||
TypeCard
|
||||
TypeCardWidget
|
||||
)
|
||||
|
||||
func (r PayoutType) String() string {
|
||||
@@ -18,6 +20,10 @@ func (r PayoutType) String() string {
|
||||
return "spb"
|
||||
case TypeYooMoney:
|
||||
return "yoo_money"
|
||||
case TypeCard:
|
||||
return "bank_card"
|
||||
case TypeCardWidget:
|
||||
return "widget"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
@@ -33,8 +39,12 @@ func (r *PayoutType) UnmarshalText(text []byte) (err error) {
|
||||
*r = TypeSBP
|
||||
case "yoo_money":
|
||||
*r = TypeYooMoney
|
||||
case "bank_card":
|
||||
*r = TypeCard
|
||||
case "widget":
|
||||
*r = TypeCardWidget
|
||||
default:
|
||||
err = fmt.Errorf("invalid payment type: %s", s)
|
||||
err = fmt.Errorf("invalid payout type: %s", s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -83,7 +93,7 @@ func (r *PayoutStatus) UnmarshalText(text []byte) (err error) {
|
||||
case "failed":
|
||||
*r = StatusFailed
|
||||
default:
|
||||
err = fmt.Errorf("invalid payment type: %s", s)
|
||||
err = fmt.Errorf("invalid payout type: %s", s)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -96,8 +106,10 @@ type SBPBank struct {
|
||||
|
||||
type PayoutReq struct {
|
||||
PayoutType PayoutType `json:"payout_type"`
|
||||
PayoutToken string `json:"payout_token"`
|
||||
AccountNumber string `json:"account_number"`
|
||||
BankID string `json:"bank_id"`
|
||||
CardNumber string `json:"card_number"`
|
||||
Amount float32 `json:"amount"`
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,9 @@ type YooKassa struct {
|
||||
|
||||
ApiBaseKey string
|
||||
ApiBaseSecret string
|
||||
ApiPaymentKey string
|
||||
ApiPaymentSecret string
|
||||
ApiPayoutKey string
|
||||
ApiPayoutSecret string
|
||||
WidgetVersion string
|
||||
|
||||
CallbackProcessTimeout time.Duration
|
||||
}
|
||||
|
||||
@@ -39,7 +39,8 @@ type Metadata map[string]any
|
||||
|
||||
type PayoutRequest struct {
|
||||
Amount Amount `json:"amount"`
|
||||
PayoutDestinationData PayoutDestination `json:"payout_destination_data"`
|
||||
PayoutToken string `json:"payout_token,omitempty"`
|
||||
PayoutDestinationData PayoutDestination `json:"payout_destination_data,omitzero"`
|
||||
Description string `json:"description"`
|
||||
Metadata Metadata `json:"metadata"`
|
||||
Test bool `json:"test"`
|
||||
|
||||
@@ -26,7 +26,7 @@ type yookassaService struct {
|
||||
func NewYookassaService(conf config.YooKassa) (Service, error) {
|
||||
client := resty.New()
|
||||
client.SetBaseURL(conf.BaseUrl)
|
||||
client.SetBasicAuth(conf.ApiPaymentKey, conf.ApiPaymentSecret)
|
||||
client.SetBasicAuth(conf.ApiPayoutKey, conf.ApiPayoutSecret)
|
||||
client.SetTimeout(conf.Timeout)
|
||||
|
||||
if conf.Retry.Enabled {
|
||||
@@ -110,10 +110,23 @@ func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.Us
|
||||
|
||||
switch req.PayoutType {
|
||||
case models.TypeSBP:
|
||||
yReq.PayoutDestinationData.Phone = userSession.Phone
|
||||
yReq.PayoutDestinationData.BankID = req.BankID
|
||||
yReq.PayoutDestinationData = PayoutDestination{
|
||||
Phone: userSession.Phone,
|
||||
BankID: req.BankID,
|
||||
}
|
||||
|
||||
case models.TypeYooMoney:
|
||||
yReq.PayoutDestinationData.AccountNumber = req.AccountNumber
|
||||
|
||||
case models.TypeCard:
|
||||
yReq.PayoutDestinationData.Card = Card{
|
||||
Number: req.CardNumber,
|
||||
}
|
||||
|
||||
case models.TypeCardWidget:
|
||||
yReq.PayoutToken = req.PayoutToken
|
||||
yReq.PayoutDestinationData = PayoutDestination{}
|
||||
|
||||
default:
|
||||
return nil, errors.New("unsupported payout type")
|
||||
}
|
||||
|
||||
45
internal/templates/payouts-widget.html
Normal file
45
internal/templates/payouts-widget.html
Normal file
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Payouts Page</title>
|
||||
<script src="https://yookassa.ru/payouts-data/{{ .WidgetVersion }}/widget.js"></script>
|
||||
<style>
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="payout-form"></div>
|
||||
|
||||
<script>
|
||||
// Инициализация виджета. Все параметры обязательные.
|
||||
const payoutsData = new window.PayoutsData({
|
||||
type: 'payout',
|
||||
account_id: '{{ .ApiPayoutKey }}', // Идентификатор шлюза (agentId в личном кабинете)
|
||||
success_callback: function(data) {
|
||||
// https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters
|
||||
if (window.AndroidCallback) {
|
||||
window.AndroidCallback.onWidgetData(JSON.stringify(data));
|
||||
} else if (window.webkit && window.webkit.messageHandlers.iosCallback) {
|
||||
window.webkit.messageHandlers.iosCallback.onWidgetData(JSON.stringify(data));
|
||||
}
|
||||
},
|
||||
error_callback: function(error) {
|
||||
// https://yookassa.ru/developers/payouts/making-payouts/bank-card/using-payout-widget/implementing-widget#reference-output-parameters-error
|
||||
if (window.AndroidCallback) {
|
||||
window.AndroidCallback.onWidgetError(JSON.stringify(error));
|
||||
} else if (window.webkit && window.webkit.messageHandlers.iosCallback) {
|
||||
window.webkit.messageHandlers.iosCallback.onWidgetError(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Отображение формы в контейнере
|
||||
payoutsData.render('payout-form')
|
||||
//Метод возвращает Promise, исполнение которого говорит о полной загрузке формы сбора данных (можно не использовать).
|
||||
.then(() => {
|
||||
//Код, который нужно выполнить после отображения формы.
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
11
internal/templates/templates.go
Normal file
11
internal/templates/templates.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
//go:embed *.html
|
||||
var FS embed.FS
|
||||
|
||||
var Templates = template.Must(template.ParseFS(FS, "*.html"))
|
||||
Reference in New Issue
Block a user