treasurer/payway/fk_api.go

621 lines
16 KiB
Go
Raw Normal View History

2023-05-16 16:21:56 +00:00
package payway
import (
"bytes"
"context"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
"bitbucket.org/skeris/treasurer/dal"
)
const UrlFk = "https://api.freekassa.ru/v1"
const PrivateKey = "SomePrivateKey1!" //16 byte
//#region ========= Payment =========
type ReqCreatePaymentUrlFk struct {
ShopId int64 `json:"shopId"` // Required
Nonce int64 `json:"nonce"` // Required Уникальный ID запроса, должен всегда быть больше предыдущего значения
Signature string `json:"signature"` // Required
PaymentId string `json:"paymentId"`
I int64 `json:"i"` // Required. ID платежной системы. dal.PaymentType.ApiId
Email string `json:"email"` // Required
IP string `json:"ip"` // Required
Amount float64 `json:"amount"` // Required
Currency string `json:"currency"` // Required
Tel string `json:"tel"`
SuccessUrl string `json:"successUrl,omitempty"`
FailureUrl string `json:"failureUrl,omitempty"`
NotificationUrl string `json:"notificationUrl,omitempty"`
}
type RespCreatePaymentUrlFk struct {
Type string `json:"type"`
OrderId int `json:"order_id"`
OrderHash string `json:"order_hash"`
Location string `json:"location"`
}
// CreatePaymentUrl - создает ссылку для перевода средств на кошелек (пополнение счета клиента)
func (fk *FreeKassa) CreatePaymentUrl(amount, orderID, paymentType, currency, lang, email, phone, requesterID,
userIP string) (string,
error) {
action := UrlFk + "/orders/create"
shopID, err := strconv.ParseInt(fk.MerchantID, 10, 64)
if err != nil {
return "", err
}
apiId, err := strconv.ParseInt(paymentType, 10, 64)
if err != nil {
return "", err
}
amountFloat, err := strconv.ParseFloat(amount, 64)
if err != nil {
return "", err
}
data := ReqCreatePaymentUrlFk{
ShopId: shopID,
Nonce: time.Now().Unix(),
Signature: "",
PaymentId: orderID,
I: apiId,
Email: email,
IP: userIP,
Amount: amountFloat,
Currency: currency,
Tel: phone,
SuccessUrl: "",
FailureUrl: "",
NotificationUrl: "",
}
body, err := fk.prepareData(data)
if err != nil {
return "", err
}
resp, err := http.Post(action, "application/json", bytes.NewReader(body))
defer func() {
resp.Body.Close()
}()
fmt.Println("Status:", resp.Status)
var result RespCreatePaymentUrlFk
bodyR, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(" ioutil: ", err)
}
fmt.Println("ERSPONSE", string(bodyR))
err = json.Unmarshal(bodyR, &result)
if err != nil {
log.Println(" JSONNNNNioutil: ", err)
}
if resp.StatusCode != 200 || err != nil {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(" ioutil: ", err)
}
return "", errors.New("bad response: " + string(body))
}
if result.Type != "success" {
return "", errors.New(fmt.Sprintf("bad response: %v", result))
}
fmt.Println(result)
return result.Location, nil
}
type ReqPaymentListenerFk struct {
MerchantID string `schema:"MERCHANT_ID"`
Amount float64 `schema:"AMOUNT"`
IntId string `schema:"intid"`
PaymentID string `schema:"MERCHANT_ORDER_ID"`
PEmail string `schema:"P_EMAIL"`
PPhone string `schema:"P_PHONE"`
CurID string `schema:"CUR_ID"`
Sign string `schema:"SIGN"`
Comission string `schema:"commission"`
PayerAccount string `schema:"payer_account"`
RequesterID string `schema:"us_requesterID"`
}
// PaymentListener - Обрабатывает результат перевода
func (fk *FreeKassa) PaymentListener(ctx context.Context, ip string, request interface{}, mc *dal.MongoConnection) error {
data := request.(*ReqPaymentListenerFk)
// Проверка IP - временно отключен, т.к. получает айпи из зиротира
//if !strings.Contains(ip, AllowedIPs) {
//return errors.New(fmt.Sprintf("this ip not allowed: %v", ip))
//}
// Проверка подписи
//fmt.Println("ORAAAA", fk.MerchantID+":"+fmt.Sprint(int64(data.Amount))+":"+fk.Secret2+":"+data.PaymentID)
//fmt.Println("ORAAAA", md5.Sum([]byte(fk.MerchantID+":"+fmt.Sprint(int64(data.Amount))+":"+fk.Secret2+":"+data.PaymentID)))
//fmt.Println("ORAAAA", fmt.Sprintf("%x",
// md5.Sum([]byte(fk.MerchantID+":"+fmt.Sprint(int64(data.Amount))+":"+fk.Secret2+":"+data.PaymentID))))
if data.Sign != fmt.Sprintf("%x",
md5.Sum([]byte(fk.MerchantID+":"+fmt.Sprint(int64(data.Amount))+":*}/^bkOCGq[C*Ss:"+data.PaymentID))) {
return errors.New(fmt.Sprintf("wrong sign: %v", data))
}
// Проверка существования платежа в БД
payment, err := mc.GetPayment(ctx, data.PaymentID)
if err != nil {
return err
}
if payment == nil {
return errors.New("payment not found")
}
// Проверка статуса платежа (должен быть open)
if payment.Status != "open" {
return errors.New(fmt.Sprintf("payment have status: %v", payment.Status))
}
// Проверка суммы платежа
//if data.Amount != payment.Amount {
// return errors.New(fmt.Sprintf("wrong amount: %v", data.Amount))
//}
//
//// Проверка RequesterID
//if data.RequesterID != payment.RequesterID {
// return errors.New(fmt.Sprintf("wrong us_requesterID: %v", data.RequesterID))
//}
// Обновление статуса платежа в БД
err = mc.UpdatePaymentStatus(ctx, data.PaymentID, "accepted")
if err != nil {
return err
}
return nil
}
type ReqGetPaymentListFk struct {
ShopId int64 `json:"shopId"` // Required
Nonce int64 `json:"nonce"` // Required
Signature string `json:"signature"` // Required
}
type RespGetPaymentListFk struct {
Type string `json:"type"`
Currencies []struct {
Id int `json:"id"`
Name string `json:"name"`
Currency string `json:"currency"`
IsEnabled int `json:"is_enabled"`
IsFavorite int `json:"is_favorite"`
} `json:"currencies"`
}
// GetPaymentList - возвращает из API актуальный список доступных платежных систем
func (fk *FreeKassa) GetPaymentList() ([]dal.PayWayPayment, error) {
action := UrlFk + "/currencies"
shopID, err := strconv.ParseInt(fk.MerchantID, 10, 64)
if err != nil {
return nil, err
}
nonce := time.Now().Unix()
data := ReqGetPaymentListFk{
ShopId: shopID,
Nonce: nonce,
}
body, err := fk.prepareData(data)
if err != nil {
return nil, err
}
resp, err := http.Post(action, "application/json", bytes.NewReader(body))
if err != nil {
fmt.Println("FKAPIERR", err)
return nil, err
}
defer func() {
resp.Body.Close()
}()
fmt.Println("Status:", resp.Status, data)
var result RespGetPaymentListFk
err = json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != 200 || err != nil {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(" ioutil: ", err)
}
return nil, errors.New("bad response: " + string(body))
}
if result.Type != "success" {
return nil, errors.New(fmt.Sprintf("bad response: %v", result))
}
fmt.Println(result)
var formatted []dal.PayWayPayment
for _, item := range result.Currencies {
status := "active"
if item.IsEnabled == 0 {
status = "inactive"
}
formatted = append(formatted, dal.PayWayPayment{
ID: fmt.Sprintf("%v-%v", fk.ShortName, item.Id),
Name: item.Name,
Status: status,
Currency: item.Currency,
ApiId: strconv.Itoa(item.Id),
Commission: 0,
Minimum: 0,
Maximum: 0,
})
}
return formatted, nil
}
//#endregion
//#region ========= Payout =========
type ReqCreatePayoutFk struct {
ShopId int64 `json:"shopId"` // Required
Nonce int64 `json:"nonce"` // Required Уникальный ID запроса, должен всегда быть больше предыдущего значения
Signature string `json:"signature"` // Required
PaymentId string `json:"paymentId"`
I int64 `json:"i"` // Required. ID платежной системы. dal.PayoutType.ApiId
Account string `json:"account"` // Required. Кошелек для зачисления средств
Amount float64 `json:"amount"` // Required
Currency string `json:"currency"` // Required
}
type RespCreatePayoutFk struct {
Type string `json:"type"`
Data struct {
Id int `json:"id"`
} `json:"data"`
}
// CreatePayout - выполняет запрос для вывода средств из кошелька (вывод средств со счета клиента).
// В ответ получает RespCreatePayoutFk
func (fk *FreeKassa) CreatePayout(amount, orderID, payoutType, currency, address string) (string, error) {
action := UrlFk + "/withdrawals/create"
payout := fk.GetPayoutType(payoutType)
if currency == "" {
currency = payout.Currency
}
shopID, err := strconv.ParseInt(fk.MerchantID, 10, 64)
if err != nil {
return "", err
}
apiId, err := strconv.ParseInt(payoutType, 10, 64)
if err != nil {
return "", err
}
amountFloat, err := strconv.ParseFloat(amount, 64)
if err != nil {
return "", err
}
data := ReqCreatePayoutFk{
ShopId: shopID,
Nonce: time.Now().Unix(),
Signature: "",
PaymentId: orderID,
I: apiId,
Account: address,
Amount: amountFloat,
Currency: currency,
}
body, err := fk.prepareData(data)
if err != nil {
return "", err
}
resp, err := http.Post(action, "application/json", bytes.NewReader(body))
defer func() {
resp.Body.Close()
}()
fmt.Println("Status:", resp.Status)
var result RespCreatePayoutFk
err = json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != 200 || err != nil {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(" ioutil: ", err)
}
return "", errors.New("bad response: " + string(body))
}
if result.Type != "success" {
return "", errors.New(fmt.Sprintf("bad response: %v", result))
}
fmt.Println(result)
return strconv.Itoa(result.Data.Id), nil
}
type ReqPayoutListenerFk struct {
WalletID string `json:"wallet_id" schema:"wallet_id"`
OrderID string `json:"order_id" schema:"order_id"`
Status string `json:"status" schema:"status"`
Amount float64 `json:"amount" schema:"amount"`
UserOrderID string `json:"user_order_id" schema:"user_order_id"`
Sign string `json:"sign" schema:"sign"`
}
// PayoutListener - обрабатывает результат вывода
func (fk *FreeKassa) PayoutListener(ctx context.Context, ip string, request interface{}, mc *dal.MongoConnection) error {
data := request.(*ReqPayoutListenerFk)
// Проверка IP - временно отключен, т.к. получает айпи из зиротира
//if !strings.Contains(ip, AllowedIPs) {
//return errors.New(fmt.Sprintf("this ip not allowed: %v", ip))
//}
// Проверка подписи
if data.Sign != fk.createSign([]string{
fk.WalletID,
data.OrderID,
data.UserOrderID,
data.Status,
fmt.Sprint(data.Amount),
}) {
return errors.New(fmt.Sprintf("wrong sign: %v", data.Sign))
}
// Проверка существования платежа в БД
payout, err := mc.GetPaymentByServiceID(ctx, data.OrderID, "false")
if err != nil {
return err
}
if payout == nil {
return errors.New("payment not found")
}
// Проверка статуса платежа (должен быть open)
if payout.Status != "open" {
return errors.New(fmt.Sprintf("payment have status: %v", payout.Status))
}
// Проверка суммы платежа
if data.Amount != payout.Amount {
return errors.New(fmt.Sprintf("wrong amount: %v", data.Amount))
}
// Обновление статуса платежа в БД
var newStatus string
switch data.Status {
case "1":
newStatus = "accepted"
case "9":
newStatus = "declined"
default:
newStatus = ""
}
err = mc.UpdatePaymentStatus(ctx, payout.ID, newStatus)
if err != nil {
return err
}
return nil
}
type ReqGetPayoutListFk struct {
ShopId int64 `json:"shopId"` // Required
Nonce int64 `json:"nonce"` // Required
Signature string `json:"signature"` // Required
}
type RespGetPayoutListFk struct {
Type string `json:"type"`
Currencies []struct {
Id int `json:"id"`
Name string `json:"name"`
Min float64 `json:"min"`
Max float64 `json:"max"`
Currency string `json:"currency"`
CanExchange int `json:"can_exchange"`
} `json:"currencies"`
}
// GetPayoutList - возвращает из API актуальный список доступных платежных систем
func (fk *FreeKassa) GetPayoutList() ([]dal.PayoutType, error) {
action := UrlFk + "/withdrawals/currencies"
shopID, err := strconv.ParseInt(fk.MerchantID, 10, 64)
if err != nil {
return nil, err
}
data := ReqGetPaymentListFk{
ShopId: shopID,
Nonce: time.Now().Unix(),
Signature: "",
}
body, err := fk.prepareData(data)
if err != nil {
return nil, err
}
resp, err := http.Post(action, "application/json", bytes.NewReader(body))
defer func() {
resp.Body.Close()
}()
fmt.Println("Status:", resp.Status)
var result RespGetPayoutListFk
err = json.NewDecoder(resp.Body).Decode(&result)
fmt.Println("StatuRR:", result, resp.StatusCode, err)
if resp.StatusCode != 200 || err != nil {
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(body)
if err != nil {
log.Println(" ioutil: ", err)
}
return nil, errors.New("bad response: " + string(body))
}
fmt.Println(result)
if result.Type != "success" {
return nil, errors.New(fmt.Sprintf("bad response: %v", result))
}
var formatted []dal.PayoutType
for _, item := range result.Currencies {
formatted = append(formatted, dal.PayoutType{
Name: item.Name,
IsEnabled: true,
Currency: item.Currency,
ApiId: strconv.Itoa(item.Id),
Commission: 0,
Minimum: item.Min,
Maximum: item.Max,
})
}
return formatted, nil
}
//#endregionn
//#region ========= Wallet =========
type ReqGetWalletBalanceFk struct {
ShopId int64 `json:"shopId"` // Required
Nonce int64 `json:"nonce"` // Required
Signature string `json:"signature"` // Required
}
type RespGetWalletBalanceFk struct {
Type string `json:"type"`
Balance []struct {
Currency string `json:"currency"`
Value float64 `json:"value"`
} `json:"balance"`
}
// GetWalletBalance - выполняет запрос чтобы узнать баланс кошелька
// В ответ получает RespGetWalletBalanceFk
func (fk *FreeKassa) GetWalletBalance() (map[string]float64, error) {
action := UrlFk + "/balance"
shopID, err := strconv.ParseInt(fk.MerchantID, 10, 64)
if err != nil {
return nil, err
}
data := ReqGetWalletBalanceFk{
ShopId: shopID,
Nonce: time.Now().Unix(),
Signature: "",
}
body, err := fk.prepareData(data)
if err != nil {
return nil, err
}
resp, err := http.Post(action, "application/json", bytes.NewReader(body))
defer func() {
resp.Body.Close()
}()
fmt.Println("Status:", resp.Status)
var result RespGetWalletBalanceFk
err = json.NewDecoder(resp.Body).Decode(&result)
if resp.StatusCode != 200 || err != nil {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Println(" ioutil: ", err)
}
return nil, errors.New("bad response: " + string(body))
}
if result.Type != "success" {
return nil, errors.New(fmt.Sprintf("bad response: %v", result))
}
fmt.Println(result)
var formatted map[string]float64
for _, item := range result.Balance {
formatted[item.Currency] = item.Value
}
return formatted, nil
}
//#endregion