diff --git a/docs/Getting started.md b/docs/Getting started.md new file mode 100644 index 0000000..7795d45 --- /dev/null +++ b/docs/Getting started.md @@ -0,0 +1,8 @@ +# Getting started + +## Installation +To add the dependency, use the following command: +`go get github.com/TheTonpay/tonpay-go-sdk` + +## Examples +For usage examples, refer to the [examples](https://github.com/TheTonpay/tonpay-go-sdk/tree/main/examples) directory. diff --git a/docs/Initialization.md b/docs/Initialization.md new file mode 100644 index 0000000..b072dc8 --- /dev/null +++ b/docs/Initialization.md @@ -0,0 +1,26 @@ +# Initialization + +Before starting to use the SDK, you need to prepare the environment. The **tonpay-go-sdk** relies on [tonutils-go](https://github.com/xssnick/tonutils-go) for interacting with the TON Blockchain. To initialize the necessary objects, follow these steps: +``` +// 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 +} +``` +Make sure to update the `configURL` based on whether you want to connect to the mainnet or testnet. diff --git a/docs/Interacting with invoice.md b/docs/Interacting with invoice.md new file mode 100644 index 0000000..5713df1 --- /dev/null +++ b/docs/Interacting with invoice.md @@ -0,0 +1,90 @@ +# Interacting with invoice + +When working with the store, you had a chance to get the invoice. Here you'll learn what you can do with it. + +## Edit an invoice +To edit an invoice, you can follow the same formula as before: +1. Connect the owner's wallet using the seed phrases: +``` +words := strings.Split("seed phrase ...", " ") +// Wallet version you can find in explorer, for example on tonscan: https://tonscan.org/address/ +w, err := wallet.FromSeed(api, words, wallet.V4R2) +if err != nil { + panic(err) +} +``` +2. Build the message cell: +``` +editInvoiceMessage, err := tonpaygo.EditInvoiceMessage(false, "", "testid", "", 700000000) +if err != nil { + panic(err) +} +``` +3. Send an internal message with the cell (ensure that the fee is `EditInvoiceFee`): +``` +message := wallet.Message{ + Mode: 1, + InternalMessage: &tlb.InternalMessage{ + Bounce: true, + DstAddr: addr, + Amount: tlb.FromNanoTONU(tonpaygo.EditInvoiceFee), + Body: editInvoiceMessage, + }, +} + +err = w.Send(context.Background(), &message, true) +if err != nil { + panic(err) +} +``` +**Warning:** Only the merchant can edit the invoice. It's important to note that an invoice cannot be edited once it has been paid or if it is inactive. + +## Manage active status +To manage the active status of an invoice, you can use the following steps: +``` +activateInvoiceMessage, err := tonpaygo.ActivateInvoiceMessage() +// or +deactivateInvoiceMessage, err := tonpaygo.DeactivateInvoiceMessage() + +// Then do step 3(send message) from previous example +``` +After confirming the transaction, the invoice will be activated/deactivated. + +**Warning:** Only the merchant can activate or deactivate the invoice. It's important to note that when the invoice is deactivated, it will not accept any payments and cannot be edited. + +## Paying the invoice +After the invoice is issued, the customer can make the payment in two ways: +1. Provide the customer with the payment link. You can find an example of this method in the [create-and-pay-invoice-url](https://github.com/TheTonpay/tonpay-go-sdk/tree/main/examples/create-and-pay-invoice-url) example. +2. Send a payment to the invoice programmatically. To do this, you can use the following steps: + +a. Get the payment message using `tonpaygo.PayInvoiceMessage()`. +``` +message := wallet.Message{ + ... + InternalMessage: &tlb.InternalMessage{ + ... + Amount: invoiceAmount + tlb.FromNanoTONU(tonpaygo.ActiveInvoiceFee), + }, +} +``` +b. Send the payment message using the same process as mentioned before. + +## Get invoice info +Like in example below you can get other contract data: +``` +func GetStore(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) +func GetMerchant(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) +func GetCustomer(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) +func HasCustomer(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) +func GetInvoiceID(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetInvoiceMetadata(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetAmount(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (uint64, error) +func IsPaid(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) +func IsActive(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) +func GetInvoiceVersion(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (uint64, error) + +``` +Or get all in one: +``` +func GetInvoiceData(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (InvoiceData, error) +``` diff --git a/docs/Interacting with store.md b/docs/Interacting with store.md new file mode 100644 index 0000000..d9cf01d --- /dev/null +++ b/docs/Interacting with store.md @@ -0,0 +1,117 @@ + +# Interacting with Store + +Congratulations on initializing the SDK! Now let's move on to interacting with stores. + +## Getting a store + +To start interacting with a store, you need to obtain the Store instance. To do that, you'll need the store ID, which can be found on your [store page](https://business.thetonpay.app/stores) in the [merchant portal](https://business.thetonpay.app/). Click the "COPY ID" button to obtain the store ID. + +![enter image description here](https://tonbyte.com/gateway/3A3420357AA219F7487DEDFB1F031A04CD9392073D8B2D9B54F34BDE06772D60/Screenshot_20230421_184454.png) + +Let's assume that our store ID is `EQCtRWrkfxovf-H02xc5CjHpC4t3VnxAIrCg6LJaaYflj4RY`. Add the following code after the initialization: +``` +storeAddress := "EQCPibUxBk7xwwcKBja6SbC5Pm1BCihGDA6SY5wTmFhrmYSh" +storeOwner, err := tonpaygo.GetOwner(api, block, storeAddress) +if err != nil { +fmt.Println(err) +return false +} + +fmt.Printf(storeName) +``` +Congratulations! You have now retrieved the store owner's address directly from the Blockchain contract. + +## Edit store +To edit store data, you need to follow these three steps: +1. Connect the owner's wallet using the seed phrases: +``` +words := strings.Split("seed phrase ...", " ") + +// Wallet version you can find in explorer, for example on tonscan: https://tonscan.org/address/ +w, err := wallet.FromSeed(api, words, wallet.V4R2) +if err != nil { + panic(err) +} +``` +2. Build the message cell: +``` +editStoreCell, err := tonpaygo.EditStoreMessage("Durger King 3.0", "Fast food", "7E5D1FDEA9EEC531F3E6BAA69E22615FB4CEA111BAE40219AB4E60053489DD64", "", 541) +if err != nil { + panic(err) +} +``` +3. Send an internal message with the cell: +``` +message := wallet.Message{ + Mode: 1, + InternalMessage: &tlb.InternalMessage{ + Bounce: true, + DstAddr: addr, + Amount: tlb.FromNanoTONU(tonpaygo.EditStoreFee), + Body: editStoreCell, + }, +} + +err = w.Send(context.Background(), &message, true) +if err != nil { + panic(err) +} +``` +**Note:** Make sure to replace "seed phrase ..." with the actual seed phrases of the owner's wallet. + +## Create invoice +To create an invoice, there are two methods available: + +1. Create a cell that stores all the required information about the payment, predicts the future invoice contract address, and packs it into a URL. You can find an example of this method [here](https://github.com/TheTonpay/tonpay-go-sdk/tree/main/examples/create-and-pay-invoice-url). This method doesn't require the store wallet seeds on the backend. + +2. Deploy the invoice contract from the backend and send its address to the customer. First, retrieve the wallet from the previous section, "Edit store": +``` +words := strings.Split("seed phrase ...", " ") + +// Wallet version you can find in explorer, for example on tonscan: https://tonscan.org/address/ +w, err := wallet.FromSeed(api, words, wallet.V4R2) +if err != nil { + panic(err) +} +``` +Then, build and send the message (ensure that you double-check the fee you send): +``` +createIssueStoreCell, err := tonpaygo.IssueInvoiceMessage(nil, false, "Invoice from go", "", tlb.MustFromTON("1").NanoTON().Uint64()) +if err != nil { + panic(err) +} + +message := wallet.Message{ + Mode: 1, + InternalMessage: &tlb.InternalMessage{ + Bounce: true, + DstAddr: addr, + Amount: tlb.FromNanoTONU(tonpaygo.IssueInvoiceFee), + Body: createIssueStoreCell, + }, +} + +err = w.Send(context.Background(), &message, true) +if err != nil { + panic(err) +} +``` + + +## Get store info +Like in example below you can get other contract data: +``` +func GetOwner(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (*address.Address, error) +func GetName(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetDescription(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetImage(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetStoreWebhook(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (string, error) +func GetMccCode(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (int64, error) +func GetIsActive(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (bool, error) +func GetStoreVersion(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (uint64, error) +``` +Or get all in one: +``` +func GetStoreData(api *ton.APIClient, block *ton.BlockIDExt, addr *address.Address) (StoreData, error) +``` diff --git a/examples/edit-store/main.go b/examples/edit-store/main.go new file mode 100644 index 0000000..8ae8a44 --- /dev/null +++ b/examples/edit-store/main.go @@ -0,0 +1,68 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + tonpaygo "github.com/TheTonpay/tonpay-go-sdk/src" + "github.com/xssnick/tonutils-go/address" + "github.com/xssnick/tonutils-go/liteclient" + "github.com/xssnick/tonutils-go/tlb" + "github.com/xssnick/tonutils-go/ton" + "github.com/xssnick/tonutils-go/ton/wallet" +) + +func EditStore(addr *address.Address, version wallet.Version, seedWords []string) bool { + 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) + + editStoreCell, err := tonpaygo.EditStoreMessage("Durger King 3.1", "Fast food", "7E5D1FDEA9EEC531F3E6BAA69E22615FB4CEA111BAE40219AB4E60053489DD64", "", 541) + if err != nil { + fmt.Println(err) + return false + } + + w, err := wallet.FromSeed(api, seedWords, wallet.V4R2) + if err != nil { + fmt.Println(err) + return false + } + + message := wallet.Message{ + Mode: 1, + InternalMessage: &tlb.InternalMessage{ + Bounce: true, + DstAddr: addr, + Amount: tlb.FromNanoTONU(tonpaygo.EditStoreFee), + Body: editStoreCell, + }, + } + + err = w.Send(context.Background(), &message, true) + if err != nil { + fmt.Println(err) + return false + } + + return true +} + +func main() { + storeAddr := address.MustParseAddr("EQBOPmMoxApwWAUW77ou70qOqqlDjCZLdqbdo6xRm3tUxP_7") + walletVersion := wallet.V4R2 + seedWords := strings.Split("seed words here ...", " ") + isUpdated := EditStore(storeAddr, walletVersion, seedWords) + fmt.Println("isUpdated:", isUpdated) +} diff --git a/src/invoice.go b/src/invoice.go index dc22654..08553b4 100644 --- a/src/invoice.go +++ b/src/invoice.go @@ -11,6 +11,14 @@ import ( "github.com/xssnick/tonutils-go/tvm/cell" ) +// OpCodes +const ( + EditInvoice = 0x48c504f3 + DeactivateInvoice = 0x1cc0b11e + ActivateInvoice = 0xc285952f + PayInvoice = 0xf53a02d3 +) + var ( DeployInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() EditInvoiceFee = tlb.MustFromTON("0.005").NanoTON().Uint64() @@ -104,6 +112,77 @@ func InvoiceConfigToCell(config InvoiceData) (*cell.Cell, error) { return cell, nil } +func EditInvoiceMessage(hasCustomer bool, customer string, invoiceID string, metadata string, amount uint64) (*cell.Cell, error) { + var customerAddress = address.MustParseAddr(ZeroAddress) + var hasCustomer_ int64 = 0 + if hasCustomer { + hasCustomer_ = -1 + customerAddress = address.MustParseAddr(customer) + + if customerAddress == nil || !customerAddress.IsAddrNone() { + return nil, fmt.Errorf("customer address is required") + } + } + + if invoiceID == "" { + return nil, fmt.Errorf("invoice ID is required") + } + + if len(invoiceID) > 120 { + return nil, fmt.Errorf("invoice ID must not be longer than 120 characters") + } + + if metadata != "" && len(metadata) > 500 { + return nil, fmt.Errorf("metadata must not be longer than 500 characters") + } + + if amount <= 0 { + return nil, fmt.Errorf("amount must be greater than 0") + } + + invoiceIDCell, _ := wallet.CreateCommentCell(invoiceID) + metadataCell, _ := wallet.CreateCommentCell(metadata) + + cell := cell.BeginCell(). + MustStoreUInt(EditInvoice, 32). + MustStoreUInt(0, 64). + MustStoreInt(hasCustomer_, 2). + MustStoreRef(cell.BeginCell().MustStoreAddr(customerAddress).EndCell()). + MustStoreRef(invoiceIDCell). + MustStoreRef(metadataCell). + MustStoreUInt(amount, 64). + EndCell() + + return cell, nil +} + +func DeactivateInvoiceMessage() (*cell.Cell, error) { + cell := cell.BeginCell(). + MustStoreUInt(DeactivateInvoice, 32). + MustStoreUInt(0, 64). + EndCell() + + return cell, nil +} + +func ActivateInvoiceMessage() (*cell.Cell, error) { + cell := cell.BeginCell(). + MustStoreUInt(ActivateInvoice, 32). + MustStoreUInt(0, 64). + EndCell() + + return cell, nil +} + +func PayInvoiceMessage() (*cell.Cell, error) { + cell := cell.BeginCell(). + MustStoreUInt(PayInvoice, 32). + MustStoreUInt(0, 64). + EndCell() + + return cell, nil +} + 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 {