621 lines
16 KiB
Go
621 lines
16 KiB
Go
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
|