mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2025-09-11 12:46:19 +00:00
yookassa import
This commit is contained in:
parent
415061bcae
commit
f360034156
6 changed files with 305 additions and 1 deletions
1
go.mod
1
go.mod
|
@ -66,6 +66,7 @@ require (
|
||||||
github.com/refraction-networking/utls v1.6.7 // indirect
|
github.com/refraction-networking/utls v1.6.7 // indirect
|
||||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||||
|
github.com/rvinnie/yookassa-sdk-go v0.0.0-20240629113713-dfd7cc31b343 // indirect
|
||||||
github.com/sagernet/sing v0.4.3 // indirect
|
github.com/sagernet/sing v0.4.3 // indirect
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
github.com/sagernet/sing-shadowsocks v0.2.7 // indirect
|
||||||
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -139,6 +139,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/rvinnie/yookassa-sdk-go v0.0.0-20240629113713-dfd7cc31b343 h1:oVZiMHABPKz0OZgx/h1BxMNkRFK4DOBdDpSrMebYmsI=
|
||||||
|
github.com/rvinnie/yookassa-sdk-go v0.0.0-20240629113713-dfd7cc31b343/go.mod h1:flatybkcu+7YLaB7mMnj9JTNKeim4jZ+ZrXNFjVA0pA=
|
||||||
github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
|
github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8=
|
||||||
github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls=
|
||||||
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8=
|
||||||
|
|
|
@ -68,6 +68,14 @@ var defaultValueMap = map[string]string{
|
||||||
"subJsonRules": "",
|
"subJsonRules": "",
|
||||||
"datepicker": "gregorian",
|
"datepicker": "gregorian",
|
||||||
"warp": "",
|
"warp": "",
|
||||||
|
"price1": "500.0",
|
||||||
|
"price2": "1400.0",
|
||||||
|
"price3": "2500.0",
|
||||||
|
"price4": "4000.0",
|
||||||
|
"month1": "1",
|
||||||
|
"month2": "3",
|
||||||
|
"month3": "6",
|
||||||
|
"month4": "12",
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingService struct{}
|
type SettingService struct{}
|
||||||
|
@ -231,6 +239,18 @@ func (s *SettingService) getInt(key string) (int, error) {
|
||||||
return strconv.Atoi(str)
|
return strconv.Atoi(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) getFloat64(key string) (float64, error) {
|
||||||
|
str, err := s.getString(key)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return strconv.ParseFloat(str, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) setFloat64(key string, value float64) error {
|
||||||
|
return s.setString(key, strconv.FormatFloat(value, 'f', -1, 64))
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) setInt(key string, value int) error {
|
func (s *SettingService) setInt(key string, value int) error {
|
||||||
return s.setString(key, strconv.Itoa(value))
|
return s.setString(key, strconv.Itoa(value))
|
||||||
}
|
}
|
||||||
|
@ -480,6 +500,130 @@ func (s *SettingService) GetSubJsonMux() (string, error) {
|
||||||
return s.getString("subJsonMux")
|
return s.getString("subJsonMux")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFirstPriceFloat() (float64, error) {
|
||||||
|
return s.getFloat64("price1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecondPriceFloat() (float64, error) {
|
||||||
|
return s.getFloat64("price2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetThirdPriceFloat() (float64, error) {
|
||||||
|
return s.getFloat64("price3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFourthPriceFloat() (float64, error) {
|
||||||
|
return s.getFloat64("price4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFirstPriceString() (string, error) {
|
||||||
|
return s.getString("price1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecondPriceString() (string, error) {
|
||||||
|
return s.getString("price2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetThirdPriceString() (string, error) {
|
||||||
|
return s.getString("price3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFourthPriceString() (string, error) {
|
||||||
|
return s.getString("price4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetAllPricesString() (string, string, string, string, error) {
|
||||||
|
price1, err := s.GetFirstPriceString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
price2, err := s.GetSecondPriceString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
price3, err := s.GetThirdPriceString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
price4, err := s.GetFourthPriceString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
return price1, price2, price3, price4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetAllPricesFloat() (float64, float64, float64, float64, error) {
|
||||||
|
price1, err := s.GetFirstPriceFloat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
price2, err := s.GetSecondPriceFloat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
price3, err := s.GetThirdPriceFloat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
price4, err := s.GetFourthPriceFloat()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, 0, 0, err
|
||||||
|
}
|
||||||
|
return price1, price2, price3, price4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFirstMonthString() (string, error) {
|
||||||
|
return s.getString("month1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecondMonthString() (string, error) {
|
||||||
|
return s.getString("month2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetThirdMonthString() (string, error) {
|
||||||
|
return s.getString("month3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFourthMonthString() (string, error) {
|
||||||
|
return s.getString("month4")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetAllMonthsString() (string, string, string, string, error) {
|
||||||
|
month1, err := s.GetFirstMonthString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
month2, err := s.GetSecondMonthString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
month3, err := s.GetThirdMonthString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
month4, err := s.GetFourthMonthString()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", "", err
|
||||||
|
}
|
||||||
|
return month1, month2, month3, month4, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFirstMonthInt() (int, error) {
|
||||||
|
return s.getInt("month1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetSecondMonthInt() (int, error) {
|
||||||
|
return s.getInt("month2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetThirdMonthInt() (int, error) {
|
||||||
|
return s.getInt("month3")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SettingService) GetFourthMonthInt() (int, error) {
|
||||||
|
return s.getInt("month4")
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SettingService) GetSubJsonRules() (string, error) {
|
func (s *SettingService) GetSubJsonRules() (string, error) {
|
||||||
return s.getString("subJsonRules")
|
return s.getString("subJsonRules")
|
||||||
}
|
}
|
||||||
|
|
|
@ -881,6 +881,7 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.clientUsage"))
|
||||||
t.getClientUsage(chatId, tgUserID)
|
t.getClientUsage(chatId, tgUserID)
|
||||||
case "admin_help":
|
case "admin_help":
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.adminContact"))
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.adminContact"))
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.adminContact"))
|
||||||
case "client_commands":
|
case "client_commands":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
||||||
|
@ -894,6 +895,38 @@ func (t *Tgbot) answerCallback(callbackQuery *telego.CallbackQuery, isAdmin bool
|
||||||
case "commands":
|
case "commands":
|
||||||
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.commands"))
|
||||||
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.commands.helpAdminCommands"))
|
||||||
|
case "payments":
|
||||||
|
price1, price2, price3, price4, err := t.settingService.GetAllPricesString()
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
month1, month2, month3, month4, err := t.settingService.GetAllMonthsString()
|
||||||
|
if err != nil {
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.answers.errorOperation"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.sendCallbackAnswerTgBot(callbackQuery.ID, t.I18nBot("tgbot.buttons.payments"))
|
||||||
|
t.SendMsgToTgbot(chatId, t.I18nBot("tgbot.answers.payments",
|
||||||
|
"Price1=="+price1,
|
||||||
|
"Month1=="+month1,
|
||||||
|
"Price2=="+price2,
|
||||||
|
"Month2=="+month2,
|
||||||
|
"Price3=="+price3,
|
||||||
|
"Month3=="+month3,
|
||||||
|
"Price4=="+price4,
|
||||||
|
"Month4=="+month4),
|
||||||
|
tu.InlineKeyboard(
|
||||||
|
tu.InlineKeyboardRow(
|
||||||
|
tu.InlineKeyboardButton(price1).WithCallbackData(t.encodeQuery("first_price_payment")),
|
||||||
|
tu.InlineKeyboardButton(price2).WithCallbackData(t.encodeQuery("second_price_payment")),
|
||||||
|
),
|
||||||
|
tu.InlineKeyboardRow(
|
||||||
|
tu.InlineKeyboardButton(price3).WithCallbackData(t.encodeQuery("third_price_payment")),
|
||||||
|
tu.InlineKeyboardButton(price4).WithCallbackData(t.encodeQuery("fourth_price_payment")),
|
||||||
|
),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -933,6 +966,12 @@ func (t *Tgbot) SendAnswer(chatId int64, msg string, isAdmin bool) {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if t.isClientExist(chatId) {
|
||||||
|
numericKeyboardClient.InlineKeyboard = append(numericKeyboardClient.InlineKeyboard, tu.InlineKeyboardRow(
|
||||||
|
tu.InlineKeyboardButton(t.I18nBot("tgbot.buttons.payments")).WithCallbackData(t.encodeQuery("payments")),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
var ReplyMarkup telego.ReplyMarkup
|
var ReplyMarkup telego.ReplyMarkup
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
ReplyMarkup = numericKeyboard
|
ReplyMarkup = numericKeyboard
|
||||||
|
@ -1457,6 +1496,17 @@ func (t *Tgbot) clientTelegramUserInfo(chatId int64, email string, messageID ...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tgbot) isClientExist(tgUserID int64) bool {
|
||||||
|
traffic, err := t.inboundService.GetClientTrafficTgBot(tgUserID)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(traffic) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
func (t *Tgbot) searchClient(chatId int64, email string, messageID ...int) {
|
||||||
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
traffic, err := t.inboundService.GetClientTrafficByEmail(email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -573,6 +573,7 @@
|
||||||
"limitTraffic" = "🚧 Лимит трафика"
|
"limitTraffic" = "🚧 Лимит трафика"
|
||||||
"getBanLogs" = "Логи блокировок"
|
"getBanLogs" = "Логи блокировок"
|
||||||
"allClients" = "Все клиенты"
|
"allClients" = "Все клиенты"
|
||||||
|
"payments" = "Продлить подписку"
|
||||||
|
|
||||||
[tgbot.answers]
|
[tgbot.answers]
|
||||||
"successfulOperation" = "✅ Успешный!"
|
"successfulOperation" = "✅ Успешный!"
|
||||||
|
@ -597,3 +598,4 @@
|
||||||
"chooseClient" = "Выберите пользователя для подключения {{ .Inbound }}"
|
"chooseClient" = "Выберите пользователя для подключения {{ .Inbound }}"
|
||||||
"chooseInbound" = "Выберите подключение"
|
"chooseInbound" = "Выберите подключение"
|
||||||
"adminContact" = "Если вам нужна помощь, свяжитесь с администратором: @sainthrill"
|
"adminContact" = "Если вам нужна помощь, свяжитесь с администратором: @sainthrill"
|
||||||
|
"payments" = "Выберите вариант оплаты подписки:\r\n💵 {{ .Price1 }} за {{ .Month1 }} месяцев\r\n💶 {{ .Price2 }} за {{ .Month2 }} месяцев\r\n💷 {{ .Price3 }} за {{ .Month3 }} месяцев\r\n💰 {{ .Price4 }} за {{ .Month4 }} месяцев"
|
105
web/yookassa/yookassa.go
Normal file
105
web/yookassa/yookassa.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package yookassa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mymmrac/telego"
|
||||||
|
"github.com/rvinnie/yookassa-sdk-go/yookassa"
|
||||||
|
yoocommon "github.com/rvinnie/yookassa-sdk-go/yookassa/common"
|
||||||
|
yoopayment "github.com/rvinnie/yookassa-sdk-go/yookassa/payment"
|
||||||
|
|
||||||
|
"x-ui/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YooKassa struct {
|
||||||
|
bot *telego.Bot
|
||||||
|
providerToken string
|
||||||
|
kassa *yookassa.Client
|
||||||
|
returnURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(bot *telego.Bot, providerToken, accountID, secretkey, returnURL string) *YooKassa {
|
||||||
|
return &YooKassa{
|
||||||
|
bot: bot,
|
||||||
|
providerToken: providerToken,
|
||||||
|
kassa: yookassa.NewClient(accountID, secretkey),
|
||||||
|
returnURL: returnURL,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *YooKassa) SendInvoice(chatID telego.ChatID, title, description string, price int) error {
|
||||||
|
_, err := k.bot.SendInvoice(&telego.SendInvoiceParams{
|
||||||
|
ChatID: chatID,
|
||||||
|
Title: title,
|
||||||
|
Description: description,
|
||||||
|
Payload: "UNIQUE_PAYLOAD",
|
||||||
|
ProviderToken: k.providerToken,
|
||||||
|
Currency: "RUB",
|
||||||
|
Prices: []telego.LabeledPrice{
|
||||||
|
{Label: title, Amount: price},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("error occurred while sending invoice", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *YooKassa) CreatePayment(amount float64, description, paymentMethod string, userID int64) (*yoopayment.Payment, error) {
|
||||||
|
paymentHandler := yookassa.NewPaymentHandler(k.kassa)
|
||||||
|
|
||||||
|
newPayment, err := paymentHandler.CreatePayment(
|
||||||
|
&yoopayment.Payment{
|
||||||
|
Amount: &yoocommon.Amount{
|
||||||
|
Value: fmt.Sprintf("%.2f", amount),
|
||||||
|
Currency: "RUB",
|
||||||
|
},
|
||||||
|
PaymentMethod: yoopayment.PaymentMethodType(paymentMethod),
|
||||||
|
Confirmation: yoopayment.Redirect{
|
||||||
|
Type: "redirect",
|
||||||
|
ReturnURL: k.returnURL,
|
||||||
|
},
|
||||||
|
Description: description,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Warning("error occurred while creating payment", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPayment, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *YooKassa) ConfirmPayment(payment *yoopayment.Payment) error {
|
||||||
|
paymentHandler := yookassa.NewPaymentHandler(k.kassa)
|
||||||
|
|
||||||
|
confirmedPayment, err := paymentHandler.CapturePayment(payment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if confirmedPayment.Status != yoopayment.Succeeded {
|
||||||
|
return fmt.Errorf("payment status is not succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *YooKassa) CancelPayment(paymentID string) error {
|
||||||
|
paymentHandler := yookassa.NewPaymentHandler(k.kassa)
|
||||||
|
|
||||||
|
_, err := paymentHandler.CancelPayment(paymentID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *YooKassa) GetPaymentInfo(paymentID string) (*yoopayment.Payment, error) {
|
||||||
|
paymentHandler := yookassa.NewPaymentHandler(k.kassa)
|
||||||
|
|
||||||
|
paymentInfo, err := paymentHandler.FindPayment(paymentID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return paymentInfo, nil
|
||||||
|
}
|
Loading…
Reference in a new issue