diff --git a/examples/check-is-invoice-paid/main.go b/examples/check-is-invoice-paid/main.go new file mode 100644 index 0000000..c71c137 --- /dev/null +++ b/examples/check-is-invoice-paid/main.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "log" + tonpaygo "tonpay-go-sdk/src" + + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/ton" +) + +func CheckIsInvoicePaid(addr *address.Address) bool { + // First of all, we need to create connection pool + client := liteclient.NewConnectionPool() + + // Choose testnet or mainnet + // configUrl := "https://ton-blockchain.github.io/global-config.json" // mainnet + configUrl := "https://ton-blockchain.github.io/testnet-global.config.json" // testnet + err := client.AddConnectionsFromConfigUrl(context.Background(), configUrl) + if err != nil { + log.Println(err) + return false + } + + api := ton.NewAPIClient(client) + + // Get block + block, err := api.CurrentMasterchainInfo(context.Background()) + if err != nil { + log.Println(err) + return false + } + + isPaid, err := tonpaygo.IsPaid(api, block, addr) + if err != nil { + fmt.Println(err) + return false + } + + return isPaid +} + +func main() { + // This one paid + addr := address.MustParseAddr("EQCPibUxBk7xwwcKBja6SbC5Pm1BCihGDA6SY5wTmFhrmYSh") + isPaid := CheckIsInvoicePaid(addr) + fmt.Println(isPaid) + + // This one not + addr2 := address.MustParseAddr("EQAO4tDMxk1NX6BXJt_LpzHSxfOGwX45M3VfL5Yqu-exS7S6") + isPaid2 := CheckIsInvoicePaid(addr2) + fmt.Println(isPaid2) +} diff --git a/examples/create-and-pay-invoice-url/main.go b/examples/create-and-pay-invoice-url/main.go new file mode 100644 index 0000000..981a1a5 --- /dev/null +++ b/examples/create-and-pay-invoice-url/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + tonpaygo "tonpay-go-sdk/src" + + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" +) + +func main() { + storeAddr := address.MustParseAddr("EQCA_bhKoqvxxG44_fBDC4VFQ36Sdzu6i8KaD2SMaZwWS_N9") + amount := tlb.MustFromTON("1").NanoTON().Uint64() + + requestCell, err := tonpaygo.RequestPurchaseMessage("created using tonpaygo, paid by wallet", amount) + if err != nil { + fmt.Println(err) + return + } + + url := tonpaygo.BuildURLForWallet(storeAddr.String(), requestCell, amount, tonpaygo.RequestPurchaseFee) + + fmt.Printf("Send this url to the customer: %s", url) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..25c22ee --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module tonpay-go-sdk + +go 1.19 + +require ( + github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect + github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect + github.com/xssnick/tonutils-go v1.6.2 // indirect + golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 // indirect + golang.org/x/sys v0.0.0-20220325203850-36772127a21f // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a5d5922 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY= +github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs= +github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA= +github.com/xssnick/tonutils-go v1.6.2 h1:K8Kp2pQ9n8i+73gCepcdf0GJnTK826ZxGWjQk4l0i4I= +github.com/xssnick/tonutils-go v1.6.2/go.mod h1:wH8ldhLueyfXW15r3MyaIq9YzA+8bzvL6UMU2BLp08g= +golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064 h1:S25/rfnfsMVgORT4/J61MJ7rdyseOZOyvLIrZEZ7s6s= +golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/sys v0.0.0-20220325203850-36772127a21f h1:TrmogKRsSOxRMJbLYGrB4SBbW+LJcEllYBLME5Zk5pU= +golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/src/code.go b/src/code.go new file mode 100644 index 0000000..a610f9a --- /dev/null +++ b/src/code.go @@ -0,0 +1,6 @@ +package tonpaygo + +const ( + InvoiceCode = "te6cckECIAEAA6IAART/APSkE/S88sgLAQIBYhcCAgEgDgMCASAJBAIBIAYFADW0mZ2omh9IH0gfSBpAOpqaZ/pAOkAmAgcL4RACASAIBwA1s2o7UTQ+kD6QPpA0gHU1NM/0gHSATAQeF8IgADWypztRND6QPpA+kDSAdTU0z/SAdIBMBBIXwiACASANCgIBSAwLAC+vbXaiaH0gfSB9IGkA6mppn+kA6QCYOkAAB67COkAAMbZ/PaiaH0gfSB9IGkA6mppn+kA6QCYL4RACASAWDwIBIBMQAgEgEhEANbO0O1E0PpA+kD6QNIB1NTTP9IB0gEwEFhfCIAAxsSZ7UTQ+kD6QPpA0gHU1NM/0gHSATBsgYAIBIBUUADOygbtRND6QPpA+kDSAdTU0z/SAdIBMBhfCIAA1s9L7UTQ+kD6QPpA0gHU1NM/0gHSATAQaF8IgADW5BB7UTQ+kD6QPpA0gHU1NM/0gHSATAQKF8IgCAs4ZGAA3TIUAnPFlAHzxZQBc8WE8oBzMzLP8oBygHJ7VSAS7QzIscAjhxsEtDTAzH6QDBwgBDIywVYzxZY+gLLasmDBvsA4NDTAwFxsJJfA+D6QDAC0x/TPzEhghD1OgLTuuMCMiCCEEjFBPO64wIgghAcwLEeuuMCIIIQwoWVL7qB0cGxoA2o4qW+1E0PpA+kD6QNIB1NTTP9IB0gEwkl8J4FGGxwWzlYEPAPLw3lUGf/AC4IIQYb3fi7qOMe1E0PpA+kD6QNIB1NTTP9IB0gEwEGhfCBLHBbOVgQ8A8vDe1NIBAZTUMO1UkTDi+wTgW4QP8vAAVlvtRND6QPpA+kDSAdTU0z/SAdIBMLOSXwngUYbHBbOVgQ8A8vDeVQZw8AIAiDDtRND6QPpA+kDSAdTU0z/SAdIBMGxCILOWggDwAfLw3iGWggDwBfLw3lFjxwWzlYEPAPLw3gTSAdTU0z8wEGhVFfACAvxb7UTQ+kD6QPpA0gHU1NM/0gHSATABf7CWggDwAvLw3lOBuZaCAPAD8vDeBI4VItBSkMcFs1OXxwWzsJaCAPAE8vDe3iCAZKkEp2JwgBDIywUnzxZdofoCy2rJcfsAcCCAEMjLBSnPFlAD+gISy2rLHyLQzxbJU4G8koMG4w0fHgCc+wBTaMcFkgLQkjIn4lNyvI4lcCCAEMjLBVADzxZRlKEZ+gLLahfLH4tmNoYW5nZYzxbJgwb7AJIwNuJ/yFAIzxbJJxBoEFdQBAYFA/ACAAJwQXVxzA==" + StoreCode = "te6ccgEBCAEAhgABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQCW8oMI1xgg0x/TH9MfAvgju/Jj7UTQ0x/TH9P/0VEyuvKhUUS68qIE+QFUEFX5EPKj+ACTINdKltMH1AL7AOgwAaTIyx/LH8v/ye1UAATQMAIBSAYHABe7Oc7UTQ0z8x1wv/gAEbjJftRNDXCx+A==" +) diff --git a/src/invoice.go b/src/invoice.go new file mode 100644 index 0000000..36e2e21 --- /dev/null +++ b/src/invoice.go @@ -0,0 +1,150 @@ +package tonpaygo + +import ( + "context" + + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" +) + +var ( + DeployInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + EditInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + ActiveInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + DeactiveInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() +) + +type InvoiceData struct { + Store string + Merchant string + Beneficiary string + HasCustomer bool + Customer string + InvoiceID string + Amount uint64 + Paid bool + Active bool + Version int64 +} + +func GetStore(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_store") + if err != nil { + return nil, err + } + + owner := res.MustSlice(0).MustLoadAddr() + return owner, nil +} + +func GetMerchant(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_merchant") + if err != nil { + return nil, err + } + + owner := res.MustSlice(0).MustLoadAddr() + return owner, nil +} + +func GetCustomer(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_customer") + if err != nil { + return nil, err + } + + customer := res.MustSlice(0).MustLoadAddr() + return customer, nil +} + +func HasCustomer(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_has_customer") + if err != nil { + return false, err + } + + hasCustomer := res.MustInt(0).Int64() + return hasCustomer == -1, nil +} + +func GetInvoiceID(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_id") + if err != nil { + return "", err + } + + id := res.MustCell(0).BeginParse().MustLoadStringSnake() + return id, nil +} + +func GetAmount(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (uint64, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_amount") + if err != nil { + return 0, err + } + + amount := res.MustInt(0) + return amount.Uint64(), nil +} + +func IsPaid(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_paid") + if err != nil { + return false, err + } + + isPaid := res.MustInt(0).Int64() + return isPaid == -1, nil +} + +func IsActive(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_active") + if err != nil { + return false, err + } + + isActive := res.MustInt(0).Int64() + return isActive == -1, nil +} + +func GetInvoiceVersion(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (uint64, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_version") + if err != nil { + return 0, err + } + + version := res.MustInt(0) + return version.Uint64(), nil +} + +func GetInvoiceData(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (InvoiceData, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_invoice_data") + if err != nil { + return InvoiceData{}, err + } + + store := res.MustSlice(0).MustLoadAddr() + merchant := res.MustSlice(1).MustLoadAddr() + beneficiary := res.MustSlice(2).MustLoadAddr() + hasCustomer := res.MustInt(3) + customer := res.MustCell(4).BeginParse().MustLoadAddr() + invoiceID := res.MustCell(5).BeginParse().MustLoadStringSnake() + amount := res.MustInt(6) + paid := res.MustInt(7) + active := res.MustInt(8) + version := res.MustInt(9) + + return InvoiceData{ + Store: store.String(), + Merchant: merchant.String(), + Beneficiary: beneficiary.String(), + HasCustomer: hasCustomer.Int64() == -1, + Customer: customer.String(), + InvoiceID: invoiceID, + Amount: amount.Uint64(), + Paid: paid.Int64() == -1, + Active: active.Int64() == -1, + Version: version.Int64(), + }, nil +} diff --git a/src/store.go b/src/store.go new file mode 100644 index 0000000..fe0ad68 --- /dev/null +++ b/src/store.go @@ -0,0 +1,297 @@ +package tonpaygo + +import ( + "context" + "fmt" + + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" + "github.com/xssnick/tonutils-go/ton/wallet" + "github.com/xssnick/tonutils-go/tvm/cell" +) + +// OpCodes +const ( + IssueInvoice = 0x4b4e70b0 + RequestPurchase = 0x36b795b5 + EditStore = 0xa0b2b61d + DeleteStore = 0xfb4aca1a // UNUSED + DeactivateStore = 0xf9bf9637 + ActivateStore = 0x97500daf + UpgradeCodeFull = 0xb43bbb52 + UpgradeCodeStore = 0xacb08f28 + UpgradeCodeInvoice = 0xb5f1424f +) + +var ( + DeployStoreFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + EditStoreFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + ActivateStoreFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + DeactivateStoreFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + IssueInvoiceFee = tlb.MustFromTON("0.02").NanoTON().Uint64() + RequestPurchaseFee = tlb.MustFromTON("0.04").NanoTON().Uint64() + FullUpgradeFee = tlb.MustFromTON("0.005").NanoTON().Uint64() + InvoiceUpgradeFee = tlb.MustFromTON("0.005").NanoTON().Uint64() +) + +type StoreData struct { + Owner *address.Address + Name string + Description string + Image string + MccCode uint64 + IsActive bool + InvoiceCode *cell.Cell + Version int64 +} + +func StoreDataToCell(storeConfig StoreData) (*cell.Cell, error) { + nameCell, err := wallet.CreateCommentCell(storeConfig.Name) + if err != nil { + return nil, err + } + descriptionCell, err := wallet.CreateCommentCell(storeConfig.Description) + if err != nil { + return nil, err + } + imageCell, err := wallet.CreateCommentCell(storeConfig.Image) + if err != nil { + return nil, err + } + + var IsActive int64 = -1 + if !storeConfig.IsActive { + IsActive = 0 + } + + return cell.BeginCell(). + MustStoreAddr(storeConfig.Owner). + MustStoreRef(nameCell). + MustStoreRef(descriptionCell). + MustStoreRef(imageCell). + MustStoreUInt(storeConfig.MccCode, 16). + MustStoreInt(IsActive, 2). + MustStoreRef(storeConfig.InvoiceCode). + EndCell(), nil +} + +func IssueInvoiceMessage(addr *address.Address, anyoneCanPay bool, comment string, amount uint64) (*cell.Cell, error) { + commentCell, err := wallet.CreateCommentCell(comment) + if err != nil { + return nil, err + } + + var hasCustomer int64 = 0 + if anyoneCanPay { + hasCustomer = -1 + } else if addr != nil || addr.IsAddrNone() { + return nil, fmt.Errorf("address is not set") + } + + addressCell := cell.BeginCell().MustStoreAddr(addr).EndCell() + return cell.BeginCell(). + MustStoreUInt(IssueInvoice, 32). + MustStoreUInt(0, 64). + MustStoreInt(hasCustomer, 2). + MustStoreRef(addressCell). + MustStoreRef(commentCell). + MustStoreUInt(amount, 64). + EndCell(), nil +} + +func RequestPurchaseMessage(invoiceComment string, amount uint64) (*cell.Cell, error) { + commentCell, err := wallet.CreateCommentCell(invoiceComment) + if err != nil { + return nil, err + } + + return cell.BeginCell(). + MustStoreUInt(RequestPurchase, 32). + MustStoreUInt(0, 64). + MustStoreRef(commentCell). + MustStoreUInt(amount, 64). + EndCell(), nil +} + +func EditStoreMessage(storeName string, storeDescription string, storeImage string, mccCode uint64) (*cell.Cell, error) { + name, err := wallet.CreateCommentCell(storeName) + if err != nil { + return nil, err + } + description, err := wallet.CreateCommentCell(storeDescription) + if err != nil { + return nil, err + } + image, err := wallet.CreateCommentCell(storeImage) + if err != nil { + return nil, err + } + + return cell.BeginCell(). + MustStoreUInt(EditStore, 32). + MustStoreUInt(0, 64). + MustStoreRef(name). + MustStoreRef(description). + MustStoreRef(image). + MustStoreUInt(mccCode, 16). + EndCell(), nil +} + +func DeactivateStoreMessage() (*cell.Cell, error) { + return cell.BeginCell(). + MustStoreUInt(DeactivateStore, 32). + MustStoreUInt(0, 64). + EndCell(), nil +} + +func ActivateStoreMessage() (*cell.Cell, error) { + return cell.BeginCell(). + MustStoreUInt(ActivateStore, 32). + MustStoreUInt(0, 64). + EndCell(), nil +} + +func UpgradeCodeFullMessage(storeCode *cell.Cell, invoiceCode *cell.Cell, hasNewData bool, newData *cell.Cell) (*cell.Cell, error) { + if hasNewData && newData == nil { + return nil, fmt.Errorf("newData is not set") + } + + var hasNewDataFlag int64 = 0 + if hasNewData { + hasNewDataFlag = -1 + } + + return cell.BeginCell(). + MustStoreUInt(UpgradeCodeFull, 32). + MustStoreUInt(0, 64). + MustStoreRef(storeCode). + MustStoreRef(invoiceCode). + MustStoreInt(hasNewDataFlag, 2). + MustStoreRef(newData). + EndCell(), nil +} + +func UpgradeCodeStoreMessage(storeCode *cell.Cell, hasNewData bool, newData *cell.Cell) (*cell.Cell, error) { + if hasNewData && newData == nil { + return nil, fmt.Errorf("newData is not set") + } + + var hasNewDataFlag int64 = 0 + if hasNewData { + hasNewDataFlag = -1 + } + + return cell.BeginCell(). + MustStoreUInt(UpgradeCodeStore, 32). + MustStoreUInt(0, 64). + MustStoreRef(storeCode). + MustStoreInt(hasNewDataFlag, 2). + MustStoreRef(newData). + EndCell(), nil +} + +func UpgradeCodeInvoiceMessage(invoiceCode *cell.Cell) (*cell.Cell, error) { + return cell.BeginCell(). + MustStoreUInt(UpgradeCodeInvoice, 32). + MustStoreUInt(0, 64). + MustStoreRef(invoiceCode). + EndCell(), nil +} + +func GetName(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_name") + if err != nil { + return "", err + } + + name := res.MustCell(0).BeginParse().MustLoadStringSnake() + return name, nil +} + +func GetDescription(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_description") + if err != nil { + return "", err + } + + description := res.MustCell(0).BeginParse().MustLoadStringSnake() + return description, nil +} + +func GetImage(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_image") + if err != nil { + return "", err + } + + image := res.MustCell(0).BeginParse().MustLoadStringSnake() + return image, nil +} + +func GetMccCode(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (int64, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_mcc_code") + if err != nil { + return -1, err + } + + mccCode := res.MustInt(0).Int64() + return mccCode, nil +} + +func GetOwner(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_owner") + if err != nil { + return nil, err + } + + owner := res.MustSlice(0).MustLoadAddr() + return owner, nil +} + +func GetIsActive(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_active") + if err != nil { + return false, err + } + + isActive := res.MustInt(0).Int64() + return isActive == -1, nil +} + +func GetStoreVersion(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (int64, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_version") + if err != nil { + return -1, err + } + + version := res.MustInt(0).Int64() + return version, nil +} + +func GetStoreData(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (StoreData, error) { + res, err := api.RunGetMethod(context.Background(), block, addr, "get_store_data") + if err != nil { + return StoreData{}, err + } + + owner := res.MustSlice(0).MustLoadAddr() + name := res.MustCell(1).BeginParse().MustLoadStringSnake() + description := res.MustCell(2).BeginParse().MustLoadStringSnake() + image := res.MustCell(3).BeginParse().MustLoadStringSnake() + mccCode := res.MustInt(4) + isActive := res.MustInt(5) + invoiceCode := res.MustCell(6) + version := res.MustInt(7) + + return StoreData{ + Owner: owner, + Name: name[4:], + Description: description[4:], + Image: image[4:], + MccCode: mccCode.Uint64(), + IsActive: isActive.Int64() == -1, + InvoiceCode: invoiceCode, + Version: version.Int64(), + }, nil +} diff --git a/src/utils.go b/src/utils.go new file mode 100644 index 0000000..49248d0 --- /dev/null +++ b/src/utils.go @@ -0,0 +1,15 @@ +package tonpaygo + +import ( + "encoding/base64" + "fmt" + + "github.com/xssnick/tonutils-go/tvm/cell" +) + +func BuildURLForWallet(reciver string, cell_ *cell.Cell, amount uint64, comission uint64) string { + bin := base64.StdEncoding.EncodeToString(cell_.ToBOCWithFlags(false)) + total := amount + comission + + return fmt.Sprintf("ton://transfer/%s?bin=%s&amount=%d", reciver, bin, total) +}