treasurer/payway/fk_api.go
2023-05-16 19:21:56 +03:00

621 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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