update payment ui and change api route path

This commit is contained in:
sojibimran
2026-04-03 22:00:24 +08:00
parent 99c4e5e072
commit 537168008c
31 changed files with 1736 additions and 963 deletions
+8 -2
View File
@@ -9,6 +9,8 @@ import (
"github.com/assimon/luuu/model/response"
)
var ErrOrder = errors.New("不存在待支付订单或已过期")
// GetCheckoutCounterByTradeId returns checkout info for a pending order.
func GetCheckoutCounterByTradeId(tradeId string) (*response.CheckoutCounterResponse, error) {
orderInfo, err := data.GetOrderInfoByTradeId(tradeId)
@@ -16,16 +18,20 @@ func GetCheckoutCounterByTradeId(tradeId string) (*response.CheckoutCounterRespo
return nil, err
}
if orderInfo.ID <= 0 || orderInfo.Status != mdb.StatusWaitPay {
return nil, errors.New("不存在待支付订单或已过期")
return nil, ErrOrder
}
resp := &response.CheckoutCounterResponse{
TradeId: orderInfo.TradeId,
Amount: orderInfo.Amount,
ActualAmount: orderInfo.ActualAmount,
ReceiveAddress: orderInfo.ReceiveAddress,
Token: orderInfo.Token,
Currency: orderInfo.Currency,
ReceiveAddress: orderInfo.ReceiveAddress,
Network: orderInfo.Network,
ExpirationTime: orderInfo.CreatedAt.AddMinutes(config.GetOrderExpirationTime()).TimestampMilli(),
RedirectUrl: orderInfo.RedirectUrl,
CreatedAt: orderInfo.CreatedAt.TimestampMilli(),
}
return resp, nil
}
+233
View File
@@ -0,0 +1,233 @@
package service
import (
"context"
"encoding/base64"
"encoding/binary"
"errors"
"fmt"
"github.com/gagliardetto/solana-go"
"github.com/gagliardetto/solana-go/rpc"
)
func SolCallBack(address string) {}
const (
// Mint token
USDT_Mint = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
USDC_Mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
)
const (
TokenProgramID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
Token2022ProgramID = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"
)
func FindATAAddress(owner, mint string) (string, error) {
ownerPubKey, err := solana.PublicKeyFromBase58(owner)
if err != nil {
return "", fmt.Errorf("invalid owner public key: %w", err)
}
mintPubKey, err := solana.PublicKeyFromBase58(mint)
if err != nil {
return "", fmt.Errorf("invalid mint public key: %w", err)
}
ata, _, err := solana.FindAssociatedTokenAddress(ownerPubKey, mintPubKey)
if err != nil {
return "", fmt.Errorf("find associated token address failed: %w", err)
}
return ata.String(), nil
}
const (
InstructionTransfer = 3
InstructionTransferChecked = 12
)
type TransferInfo struct {
ProgramID string
Type string
Source string
Destination string
Mint string
Authority string
Amount uint64
Decimals *uint8
}
func isTokenProgram(programID solana.PublicKey) bool {
p := programID.String()
return p == TokenProgramID || p == Token2022ProgramID
}
func DecodeTransfer(data []byte, accountKeys []solana.PublicKey, accountIndexes []uint16, programID solana.PublicKey) (*TransferInfo, error) {
if len(data) < 9 {
return nil, fmt.Errorf("invalid transfer data length: %d", len(data))
}
if data[0] != InstructionTransfer {
return nil, fmt.Errorf("not transfer instruction, got: %d", data[0])
}
if len(accountIndexes) < 3 {
return nil, fmt.Errorf("transfer accounts not enough: %d", len(accountIndexes))
}
amount := binary.LittleEndian.Uint64(data[1:9])
sourceIdx := accountIndexes[0]
destIdx := accountIndexes[1]
authIdx := accountIndexes[2]
if int(sourceIdx) >= len(accountKeys) || int(destIdx) >= len(accountKeys) || int(authIdx) >= len(accountKeys) {
return nil, errors.New("account index out of range")
}
info := &TransferInfo{
ProgramID: programID.String(),
Type: "transfer",
Source: accountKeys[sourceIdx].String(),
Destination: accountKeys[destIdx].String(),
Authority: accountKeys[authIdx].String(),
Amount: amount,
}
return info, nil
}
type TransferCheckedInfo struct {
ProgramID string
Type string
Source string
Destination string
Mint string
Authority string
Amount uint64
Decimals uint8
}
func DecodeTransferCheck(data []byte, accountKeys []solana.PublicKey, accountIndexes []uint16, programID solana.PublicKey) (*TransferInfo, error) {
if len(data) < 10 {
return nil, fmt.Errorf("invalid transferChecked data length: %d", len(data))
}
if data[0] != InstructionTransferChecked {
return nil, fmt.Errorf("not transferChecked instruction, got: %d", data[0])
}
if len(accountIndexes) < 4 {
return nil, fmt.Errorf("transferChecked accounts not enough: %d", len(accountIndexes))
}
amount := binary.LittleEndian.Uint64(data[1:9])
decimals := uint8(data[9])
sourceIdx := accountIndexes[0]
mintIdx := accountIndexes[1]
destIdx := accountIndexes[2]
authIdx := accountIndexes[3]
if int(sourceIdx) >= len(accountKeys) ||
int(mintIdx) >= len(accountKeys) ||
int(destIdx) >= len(accountKeys) ||
int(authIdx) >= len(accountKeys) {
return nil, errors.New("account index out of range")
}
info := &TransferInfo{
ProgramID: programID.String(),
Type: "transferChecked",
Source: accountKeys[sourceIdx].String(),
Mint: accountKeys[mintIdx].String(),
Destination: accountKeys[destIdx].String(),
Authority: accountKeys[authIdx].String(),
Amount: amount,
Decimals: &decimals,
}
return info, nil
}
// ParseTransactionTransfers
func ParseTransactionTransfers(ctx context.Context, client *rpc.Client, sig string) ([]*TransferInfo, error) {
signature, err := solana.SignatureFromBase58(sig)
if err != nil {
return nil, fmt.Errorf("invalid signature: %w", err)
}
tx, err := client.GetTransaction(
ctx,
signature,
&rpc.GetTransactionOpts{
Encoding: solana.EncodingBase64,
Commitment: rpc.CommitmentConfirmed,
MaxSupportedTransactionVersion: func(v uint64) *uint64 { return &v }(0),
},
)
if err != nil {
return nil, fmt.Errorf("get transaction failed: %w", err)
}
if tx == nil {
return nil, errors.New("transaction not found")
}
decodedTx, err := tx.Transaction.GetTransaction()
if err != nil {
return nil, fmt.Errorf("decode transaction failed: %w", err)
}
msg := decodedTx.Message
accountKeys := msg.AccountKeys
results := make([]*TransferInfo, 0)
for _, ix := range msg.Instructions {
if int(ix.ProgramIDIndex) >= len(accountKeys) {
continue
}
programID := accountKeys[ix.ProgramIDIndex]
if !isTokenProgram(programID) {
continue
}
data := ix.Data
if len(data) == 0 {
continue
}
switch data[0] {
case InstructionTransfer:
info, err := DecodeTransfer(data, accountKeys, toUint16Slice(ix.Accounts), programID)
if err == nil {
results = append(results, info)
}
case InstructionTransferChecked:
info, err := DecodeTransferCheck(data, accountKeys, toUint16Slice(ix.Accounts), programID)
if err == nil {
results = append(results, info)
}
}
}
return results, nil
}
func toUint16Slice(in []uint16) []uint16 {
out := make([]uint16, len(in))
for i, v := range in {
out[i] = uint16(v)
}
return out
}
func DecodeBase64InstructionData(s string) ([]byte, error) {
return base64.StdEncoding.DecodeString(s)
}
+45
View File
@@ -0,0 +1,45 @@
package service
import (
"context"
"fmt"
"testing"
"github.com/gagliardetto/solana-go/rpc"
)
func TestParseSolTransaction(t *testing.T) {
client := rpc.New("https://api.mainnet-beta.solana.com")
sig := ""
txInfo, err := ParseTransactionTransfers(context.Background(), client, sig)
if err != nil {
panic(err)
}
for _, item := range txInfo {
fmt.Println("type =", item.Type)
fmt.Println("program =", item.ProgramID)
fmt.Println("from =", item.Source)
fmt.Println("to =", item.Destination)
fmt.Println("mint =", item.Mint)
fmt.Println("authority =", item.Authority)
fmt.Println("amount =", item.Amount)
if item.Decimals != nil {
fmt.Println("decimals =", *item.Decimals)
}
fmt.Println("-----")
}
}
func TestFindATAAddress(t *testing.T) {
owner := ""
mint := "4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R" // ray token
ata, err := FindATAAddress(owner, mint)
if err != nil {
panic(err)
}
fmt.Println("ATA =", ata)
}