mirror of
https://github.com/GMWalletApp/epusdt.git
synced 2026-07-07 18:26:16 +00:00
feat: integrate okpay switch-network flow and notify handling
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GMWalletApp/epusdt/model/data"
|
||||
"github.com/GMWalletApp/epusdt/model/request"
|
||||
"github.com/GMWalletApp/epusdt/util/constant"
|
||||
"github.com/GMWalletApp/epusdt/util/http_client"
|
||||
"github.com/GMWalletApp/epusdt/util/log"
|
||||
)
|
||||
|
||||
type okPayCreateDepositResponse struct {
|
||||
Status string `json:"status"`
|
||||
Code int `json:"code"`
|
||||
Data struct {
|
||||
OrderID string `json:"order_id"`
|
||||
PayURL string `json:"pay_url"`
|
||||
} `json:"data"`
|
||||
Msg string `json:"msg"`
|
||||
}
|
||||
|
||||
type okPayDepositOrder struct {
|
||||
ProviderOrderID string
|
||||
PayURL string
|
||||
}
|
||||
|
||||
type okPayNotifyPayload struct {
|
||||
ID string `json:"id"`
|
||||
Status string `json:"status"`
|
||||
Code string `json:"code"`
|
||||
OrderID string `json:"order_id"`
|
||||
UniqueID string `json:"unique_id"`
|
||||
PayUserID string `json:"pay_user_id"`
|
||||
Amount string `json:"amount"`
|
||||
Coin string `json:"coin"`
|
||||
PayStatus string `json:"pay_status"`
|
||||
NotifyType string `json:"notify_type"`
|
||||
Sign string `json:"sign"`
|
||||
RawFormData string `json:"raw_form_data"`
|
||||
}
|
||||
|
||||
func okPaySign(form map[string]string, shopID string, shopToken string) map[string]string {
|
||||
values := url.Values{}
|
||||
for key, value := range form {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
continue
|
||||
}
|
||||
values.Set(key, value)
|
||||
}
|
||||
values.Set("id", shopID)
|
||||
query, _ := url.QueryUnescape(values.Encode())
|
||||
sum := md5.Sum([]byte(query + "&token=" + shopToken))
|
||||
|
||||
signed := make(map[string]string, len(values)+1)
|
||||
for key, vals := range values {
|
||||
if len(vals) == 0 {
|
||||
continue
|
||||
}
|
||||
signed[key] = vals[0]
|
||||
}
|
||||
signed["sign"] = strings.ToUpper(hex.EncodeToString(sum[:]))
|
||||
return signed
|
||||
}
|
||||
|
||||
func createOkPayDepositOrder(uniqueID string, amount float64, coin string, returnURL string) (*okPayDepositOrder, error) {
|
||||
shopID := data.GetOkPayShopID()
|
||||
shopToken := data.GetOkPayShopToken()
|
||||
apiURL := strings.TrimSpace(data.GetOkPayAPIURL())
|
||||
callbackURL := data.GetOkPayCallbackURL()
|
||||
if shopID == "" || shopToken == "" || apiURL == "" || callbackURL == "" {
|
||||
return nil, fmt.Errorf("okpay config incomplete")
|
||||
}
|
||||
|
||||
if returnURL == "" {
|
||||
returnURL = data.GetOkPayReturnURL()
|
||||
}
|
||||
|
||||
form := map[string]string{
|
||||
"unique_id": uniqueID,
|
||||
"name": uniqueID,
|
||||
"amount": fmt.Sprintf("%.2f", amount),
|
||||
"coin": strings.ToUpper(strings.TrimSpace(coin)),
|
||||
"callback_url": callbackURL,
|
||||
"return_url": returnURL,
|
||||
}
|
||||
form = okPaySign(form, shopID, shopToken)
|
||||
|
||||
base, err := url.Parse(apiURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
base.Path = path.Join(base.Path, "payLink")
|
||||
|
||||
client := http_client.GetHttpClient()
|
||||
client.SetTimeout(time.Duration(data.GetOkPayTimeoutSeconds()) * time.Second)
|
||||
|
||||
var payload okPayCreateDepositResponse
|
||||
resp, err := client.R().
|
||||
SetFormData(form).
|
||||
SetResult(&payload).
|
||||
Post(base.String())
|
||||
if err != nil {
|
||||
log.Sugar.Warnf("[okpay] create order request failed unique_id=%s api_url=%s callback_url=%s return_url=%s err=%v", uniqueID, base.String(), callbackURL, returnURL, err)
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode() >= 400 {
|
||||
log.Sugar.Warnf("[okpay] create order http error unique_id=%s status=%s api_url=%s callback_url=%s return_url=%s body=%s", uniqueID, resp.Status(), base.String(), callbackURL, returnURL, resp.String())
|
||||
return nil, fmt.Errorf("okpay http status: %s", resp.Status())
|
||||
}
|
||||
if payload.Data.OrderID == "" || payload.Data.PayURL == "" {
|
||||
log.Sugar.Warnf("[okpay] create order rejected unique_id=%s api_url=%s callback_url=%s return_url=%s status=%s code=%d msg=%s", uniqueID, base.String(), callbackURL, returnURL, payload.Status, payload.Code, payload.Msg)
|
||||
if payload.Msg != "" {
|
||||
return nil, fmt.Errorf("okpay create order failed: %s (callback_url=%s)", payload.Msg, callbackURL)
|
||||
}
|
||||
return nil, fmt.Errorf("okpay create order failed (callback_url=%s)", callbackURL)
|
||||
}
|
||||
|
||||
return &okPayDepositOrder{
|
||||
ProviderOrderID: payload.Data.OrderID,
|
||||
PayURL: payload.Data.PayURL,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func verifyOkPayNotify(form map[string]string) bool {
|
||||
shopID := data.GetOkPayShopID()
|
||||
shopToken := data.GetOkPayShopToken()
|
||||
signature := strings.TrimSpace(form["sign"])
|
||||
if shopID == "" || shopToken == "" || signature == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
if expected := okPayNotifySign(form, shopToken); expected != "" && strings.EqualFold(expected, signature) {
|
||||
return true
|
||||
}
|
||||
|
||||
unsigned := make(map[string]string, len(form))
|
||||
for key, value := range form {
|
||||
if strings.EqualFold(key, "sign") {
|
||||
continue
|
||||
}
|
||||
unsigned[key] = value
|
||||
}
|
||||
signed := okPaySign(unsigned, shopID, shopToken)
|
||||
return strings.EqualFold(strings.TrimSpace(signed["sign"]), signature)
|
||||
}
|
||||
|
||||
func okPayNotifySign(form map[string]string, shopToken string) string {
|
||||
orderedKeys := []string{
|
||||
"code",
|
||||
"data[order_id]",
|
||||
"data[unique_id]",
|
||||
"data[pay_user_id]",
|
||||
"data[amount]",
|
||||
"data[coin]",
|
||||
"data[status]",
|
||||
"data[type]",
|
||||
"id",
|
||||
"status",
|
||||
}
|
||||
|
||||
pairs := make([]string, 0, len(orderedKeys))
|
||||
for _, key := range orderedKeys {
|
||||
value := strings.TrimSpace(form[key])
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, url.QueryEscape(key)+"="+url.QueryEscape(value))
|
||||
}
|
||||
if len(pairs) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
query, err := url.QueryUnescape(strings.Join(pairs, "&"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
sum := md5.Sum([]byte(query + "&token=" + shopToken))
|
||||
return strings.ToUpper(hex.EncodeToString(sum[:]))
|
||||
}
|
||||
|
||||
func parseOkPayNotify(form map[string]string, rawFormData string) *okPayNotifyPayload {
|
||||
return &okPayNotifyPayload{
|
||||
ID: strings.TrimSpace(form["id"]),
|
||||
Status: strings.TrimSpace(form["status"]),
|
||||
Code: strings.TrimSpace(form["code"]),
|
||||
OrderID: strings.TrimSpace(form["data[order_id]"]),
|
||||
UniqueID: strings.TrimSpace(form["data[unique_id]"]),
|
||||
PayUserID: strings.TrimSpace(form["data[pay_user_id]"]),
|
||||
Amount: strings.TrimSpace(form["data[amount]"]),
|
||||
Coin: strings.TrimSpace(form["data[coin]"]),
|
||||
PayStatus: strings.TrimSpace(form["data[status]"]),
|
||||
NotifyType: strings.TrimSpace(form["data[type]"]),
|
||||
Sign: strings.TrimSpace(form["sign"]),
|
||||
RawFormData: rawFormData,
|
||||
}
|
||||
}
|
||||
|
||||
func HandleOkPayNotify(form map[string]string, rawFormData string) error {
|
||||
if !verifyOkPayNotify(form) {
|
||||
return fmt.Errorf("invalid okpay notify sign")
|
||||
}
|
||||
|
||||
payload := parseOkPayNotify(form, rawFormData)
|
||||
|
||||
if payload.ID == "" || payload.ID != data.GetOkPayShopID() {
|
||||
return fmt.Errorf("okpay notify shop id mismatch: %s", payload.ID)
|
||||
}
|
||||
if payload.UniqueID == "" || payload.OrderID == "" {
|
||||
return fmt.Errorf("missing okpay notify identifiers")
|
||||
}
|
||||
|
||||
order, err := data.GetOrderInfoByTradeId(payload.UniqueID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if order.ID <= 0 {
|
||||
return fmt.Errorf("okpay notify order not found: %s", payload.UniqueID)
|
||||
}
|
||||
if order.PayProvider != "okpay" {
|
||||
return fmt.Errorf("okpay notify order provider mismatch: %s", payload.UniqueID)
|
||||
}
|
||||
|
||||
providerRow, err := data.GetProviderOrderByTradeIDAndProvider(payload.UniqueID, order.PayProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if providerRow.ID <= 0 {
|
||||
return fmt.Errorf("okpay provider order not found: %s", payload.UniqueID)
|
||||
}
|
||||
|
||||
_ = data.SaveProviderOrderNotify(payload.UniqueID, order.PayProvider, payload.RawFormData)
|
||||
|
||||
if !strings.EqualFold(payload.Status, "success") || !strings.EqualFold(payload.NotifyType, "deposit") || payload.PayStatus != "1" {
|
||||
log.Sugar.Infof("[okpay] notify ignored trade_id=%s status=%s pay_status=%s type=%s", payload.UniqueID, payload.Status, payload.PayStatus, payload.NotifyType)
|
||||
return nil
|
||||
}
|
||||
|
||||
if providerRow.ProviderOrderID != "" && providerRow.ProviderOrderID != payload.OrderID {
|
||||
return fmt.Errorf("okpay provider order id mismatch: got=%s want=%s", payload.OrderID, providerRow.ProviderOrderID)
|
||||
}
|
||||
if !strings.EqualFold(strings.TrimSpace(order.Token), strings.TrimSpace(payload.Coin)) {
|
||||
return fmt.Errorf("okpay coin mismatch: got=%s want=%s", payload.Coin, order.Token)
|
||||
}
|
||||
|
||||
notifyAmount, err := strconv.ParseFloat(payload.Amount, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid okpay amount: %w", err)
|
||||
}
|
||||
if fmt.Sprintf("%.2f", notifyAmount) != fmt.Sprintf("%.2f", order.ActualAmount) {
|
||||
return fmt.Errorf("okpay amount mismatch: got=%.2f want=%.2f", notifyAmount, order.ActualAmount)
|
||||
}
|
||||
|
||||
err = OrderProcessing(&request.OrderProcessingRequest{
|
||||
ReceiveAddress: order.ReceiveAddress,
|
||||
Currency: order.Currency,
|
||||
Token: order.Token,
|
||||
Network: order.Network,
|
||||
Amount: order.ActualAmount,
|
||||
TradeId: order.TradeId,
|
||||
BlockTransactionId: payload.OrderID,
|
||||
})
|
||||
if err != nil && err != constant.OrderBlockAlreadyProcess {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = data.MarkProviderOrderPaid(payload.UniqueID, order.PayProvider, payload.RawFormData); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user