DEV Community

Cover image for How to Code a PayNow Function with Solidity: The 3 Must-Know Smart Contract Methods
Gospel Darlington
Gospel Darlington

Posted on • Edited on

How to Code a PayNow Function with Solidity: The 3 Must-Know Smart Contract Methods

Introduction

The web3 economy is booming and the best time to learn this technology is now. You can’t afford to miss out on this global demand for web3 developers which gets me so excited talking about.

According to this website, the average salary of a blockchain developer is around $146,250 annually which is the highest in comparison to other software development careers.

You may be saying, I have not gained much experience yet to earn that much, let me tell you. The need for web3 developers is so high right now that if you dedicate the next 3 months to learning and building blockchain apps you will get a profitable job in the shortest time.

How do I get started? you may ask, you do that by building one dApp at a time. Get in touch with me if you need private tutoring on blockchain development.

Now let’s get into the meat of this tutorial…

Check out my YouTube channel for FREE web3 tutorials now.

Why you should master payment processing in Solidity

Blockchain programming language such as Solidity is the kind of programming language built with the intent of processing money. They are built to revolutionize how we transact businesses online. You can’t say the same for other programming languages such as Java, C++, PHP, etc.

Right from the onset, programming smart contracts with Solidity requires that you know how to handle the movement of money between two or more accounts.

One funny thing about mastering payment processing as a blockchain developer is that you can take over all the jobs of bank officials in all their branches with one piece of a smart contract.

With smart contracts, all the accounting processes become automated rendering the need for bank personnel useless.

On the downside, quite a lot of hacks have been going on specifically exploiting weaknesses in how payment is made in a smart contract.

In this tutorial, you will learn about three functions that will help you move money from one account to the other and the recommended method for you. Let’s see it from this smart contract example below.

PayNow Smart Contract Example

The code snippet below is a BookStore smart contract example that allows people to sell their books online. The example includes three functions for processing payments on your smart contract.

Step by step, let me break it down for you all starting from the top…

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract BookStore {
  // Codes goes here...
}
Enter fullscreen mode Exit fullscreen mode

If you’re new to Solidity smart contract development, you have no worries. The piece of code above represents the basic structure of a smart contract.

The license identifier is very important to the solidity compiler, it complains when you don’t include it. Check out this site for all the recognized licenses in solidity.

We specified the range of solidity compilers that can successfully compile our smart contract. Lastly, we supplied the contract name as BookStore.

// Contains global store data
uint256 taxFee;
address immutable taxAccount;
uint8 totalSupply = 0;
Enter fullscreen mode Exit fullscreen mode

We included some state variables at the beginning of the smart contract that will hold the tax fee the bookstore charges sellers per sale. We included a tax account that serves as the receiving address for all taxes levied on this store. The total supply variable is a counter that keeps track of the total number of books in our store.

// Specifies book information
struct BookStruct {
    uint8 id;
    address seller;
    string title;
    string description;
    string author;
    uint256 cost;
    uint256 timestamp;
}
Enter fullscreen mode Exit fullscreen mode

Following that is a book structure, which is a complex data structure for storing information about a book. A book has an Id, a seller, a title, a description, an author, a price, and a timestamp that indicates when it was added to our store.

// Associates books with sellers and buyers
BookStruct[] books;
mapping(address => BookStruct[]) booksOf;
mapping(uint8 => address) public sellerOf;
mapping(uint8 => bool) bookExists;
Enter fullscreen mode Exit fullscreen mode

Now, in the code above, we used the newly created book structure to specify an array of the required data to be collected on the store.

So, the books variable is an array of books, and the booksOf mapping contains all of the books owned by a specific buyer. The sellerOf mapping returns a seller's address as identified by the book's Id. Finally, the bookExists mapping returns true or false if our store has a book with the Id specified.

// Logs out sales record
event Sale(
    uint8 id,
    address indexed buyer,
    address indexed seller,
    uint256 cost,
    uint256 timestamp
);

// Logs out created book record
event Created(
    uint8 id,
    address indexed seller,
    uint256 timestamp
);
Enter fullscreen mode Exit fullscreen mode

The above codes are EVM (Ethereum Virtual Machine) events that will be emitted or logged whenever a seller adds a book to our store or a buyer purchase a book from our store.

// Initializes tax on book sale
constructor(uint256 _taxFee) {
    taxAccount = msg.sender;
    taxFee = _taxFee;
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we specified the tax account to be the address of the one who deployed the smart contract. And you must know that once this is assigned during deployment, it can no longer be changed, this is why we attached the modifier called immutable. The tax fee is the percentage we want the smart contract to charge per sale of any book in our store.

// Performs book creation
function createBook(
    string memory title, 
    string memory description, 
    string memory author, 
    uint256 cost
) public returns (bool) {
    require(bytes(title).length > 0, "Title empty");
    require(bytes(description).length > 0, "Description empty");
    require(bytes(author).length > 0, "Description empty");
    require(cost > 0 ether, "Price cannot be zero");

    // Adds book to shop
    books.push(
        BookStruct(
            totalSupply++,
            msg.sender,
            title,
            description,
            author,
            cost,
            block.timestamp
        )
    );

    // Records book selling detail
    sellerOf[totalSupply] = msg.sender;
    bookExists[totalSupply] = true;

    emit Created(
        totalSupply,
        msg.sender,
        block.timestamp
    );
    return true;
}
Enter fullscreen mode Exit fullscreen mode

The function above collects a book’s information such as the seller’s address, the title, description, and the cost of a book.

Next, it carries out validations to check that the seller is not supplying empty data to our smart contract. Afterward, it pushes the book to the books array using our previously defined book structure.

Immediately after adding a book to the store, important records are kept to keep track of those that have bought the book and the seller of that book.

Lastly, before the function completes with a Boolean value, it emits or logs off some vital information to the EVM.

// Performs book payment
function payForBook(uint8 id)
    public payable returns (bool) {
    require(bookExists[id], "Book does not exist");
    require(msg.value >= books[id - 1].cost, "Ethers too small");

    // Computes payment data
    address seller = sellerOf[id];
    uint256 tax = (msg.value / 100) * taxFee;
    uint256 payment = msg.value - tax;

    // Bills buyer on book sale
    payTo(seller, payment);
    payTo(taxAccount, tax);

    // Gives book to buyer
    booksOf[msg.sender].push(books[id - 1]);

    emit Sale(
        id,
        msg.sender,
        seller,
        payment,
        block.timestamp
    );

    return true;
}
Enter fullscreen mode Exit fullscreen mode

Now, this is the most important function in this smart contract, because it is the function that makes money for you when you hook it up to a front end.

When called, the function takes in a book Id, checks if the book exists or not, and also checks if you sent enough ethers for purchasing the book.

Next, the function performs some payment computation and sends the payment percentages to the parties on the receiving end.

Once the payment has been completed, the book is given to the buyer, and that sales information is logged off on the EVM.

// Returns books of buyer
function myBooks(address buyer)
    external view returns (BookStruct[] memory) {
    return booksOf[buyer];
}
Enter fullscreen mode Exit fullscreen mode

This function returns a list of books purchased by a particular buyer. The buyers address must be supplied when using this function.

// Returns books in store
function getBooks()
    external view returns (BookStruct[] memory) {
    return books;
}
Enter fullscreen mode Exit fullscreen mode

The above function returns all the books added to our store. Anyone can call this function from our smart contract and it will retrieve and present all the books in the BookStruct array format.

// Returns a specific book by id
function getBook(uint8 id)
    external view returns (BookStruct memory) {
    return books[id - 1];
}
Enter fullscreen mode Exit fullscreen mode

Lastly, this function returns a book from our store. The caller must specify the book’s Id for successful retrieval.

Now that we are done discussing the various properties and functions that make up our smart contract, it's time to discuss the payment processing functions. The functions below enable you to perform transactions with Ethers, but which one is better? Let’s find out.

The 3 Must Know Smart Contract Functions

transferTo() function with Solidity

// Method 1: The transfer function
function transferTo(
    address to,
    uint256 amount
) internal returns (bool) {
    payable(to).transfer(amount);
    return true;
}
Enter fullscreen mode Exit fullscreen mode

This function has an inbuilt required method, so it does not need an extra. It is clean and speedily helps you transfer a certain amount of ethers to a specified address.

sendTo() function with Solidity

// Method 2: The send function
function sendTo(
    address to, 
    uint256 amount
) internal returns (bool) {
    require(payable(to).send(amount), "Payment failed");
    return true;
}
Enter fullscreen mode Exit fullscreen mode

Now, this function adds the required method for validating that payments are successfully executed. It is also simple and clean as the transferTo function.

payTo() function with Solidity

// Method 3: The call function
function payTo(
    address to, 
    uint256 amount
) internal returns (bool) {
    (bool success,) = payable(to).call{value: amount}("");
    require(success, "Payment failed");
    return true;
}
Enter fullscreen mode Exit fullscreen mode

This is a little bit more complicated than the previous functions, but it gets the job done flawlessly.

transferTo() vs sendTo() vs payTo()

I know you may be confused about which one to use in your next smart contract, however, you should also know the pros and cons of using them.

The transferTo function:
This function uses the transfer method as seen in the code below.

receiver.transfer(amount);
Enter fullscreen mode Exit fullscreen mode

Two conditions cause the transfer function to fail:

  • If the sending smart contract's balance is insufficient, the receiving contract rejects the payment.
  • In the event of a failure, the transfer function reverts.

In the event of a failure, the transfer function reverts. When a payment is made, the receiving contract's fallback() or receive() functions are called. This allows the receiving contract to respond to a payment.

2300 gas is transferred to the receiving contract. This is a very small amount, but it is sufficient to cause an event. It is not enough to simply run complex code.

The sendTo function:
This function uses the send method as seen in the code below.

receiver.send(amount);
Enter fullscreen mode Exit fullscreen mode

Send and transfer are almost synonymous in behavior. However, if the payment fails, it will not be reverted. It instead returns false. The calling contract is in charge of handling failures.

If a payment is made, the receiving contract's fallback() or receive() functions are called. This allows the receiving contract to respond to a payment.
Send also sends 2300 gallons of gas to the receiving contract.

The payTo function:
This function uses the call method as seen in the code below.

(bool success,) = receiver.call{value: amount}("");
Enter fullscreen mode Exit fullscreen mode

The call() function is meant for more individualized interactions between smart contracts. It has the ability to call a function by name and send Ether to it. It is now the preferred method for sending Ether from a smart contract.

Watch my FREE web3 tutorials on YouTube now.

Conclusion

Finally, we have come to the completion of this tutorial and hopefully, you got value from it.

I will like you to know that this is what I do, I help people come up to speed with Solidity, and if you want to get on the ride with me, I currently teach web3 development on an hourly basis. You can book a session with me here.

Thanks for reading, please hit the like, clap, or heart button to show some love, I will see you in the next tutorial…

About the Author

Gospel Darlington kick-started his journey as a software engineer in 2016. Over the years, he has grown full-blown skills in JavaScript stacks such as React, ReactNative, NextJs, and now blockchain.

He is currently freelancing, building apps for clients, and writing technical tutorials teaching others how to do what he does.

Gospel Darlington is open and available to hear from you. You can reach him on LinkedIn, Facebook, Github, or on his website.

Top comments (0)