Skip to main content

Build with SFPY - Part 1

On SFPY, anyone can integrate free decentralized payments in a traditional commerce setting using our APIs built on top of Ethereum and our payments protocol. This tutorial series will focus on developers looking to add crypto payments acceptance to their websites through an easy to use REST API.

The following topics will be covered but will be broken up into parts for brevity:

  1. Installing the go-sfpy library
  2. Initialize the client.
  3. Exposing an endpoint to create a new order
  4. Redirecting the customer to complete the payment.

I hope you find this useful and interesting enough to follow along. Future tutorials will cover how to integrate payments through smart contracts to showcase the flexibility of SFPY.

If you want to skip right ahead to a repository I have published you can head on over here

Part 1 - Installing the go-sfpy library#

Assuming you have a go service that you're working on, you can easily install our native SDK. Installation instructions are available here but I'll repeat them for convenience.

In your terminal, cd to the root of your service and make sure it is using Go Modules. If not, you can convert your service into a module by typing

go mod init <github.com/your-org/your-repo-name>

Then, reference go-sfpy in a Go file with import:

import (
"github.com/sfpyhub/go-sfpy/sfpy"
)

Alternatively, you can also explicitly go get the package into a project:

go get -u "github.com/sfpyhub/go-sfpy"

Part 2 - Initialize the sfpy client#

To use the SFPY client to make API requests, initialize it like so from within your service

client := sfpy.NewClient(apikey, secretkey)

NewClient accepts two arguments:

  • apikey: This is your SFPY Api key that is needed to authenticate all requests to the API. You can find this key on your dashboard.
  • secretkey: This is your webhook shared secret key used to validate whether incoming signatures from webhooks are valid.

Its almost always better to initialize this as a singleton when your service is starting up and pass it as an argument to your request handlers. For instance if you have a controller that handles incoming requests to create new, unpaid orders, you can pass the client as an argument like so:

Service handler struct#

type checkoutService struct {
db store.Database
logger log.Logger
... any extra properties
sfpy *sfpy.Client
}
func NewCheckoutService(
db store.Database,
logger log.Logger,
...,
sfpy *sfpy.Client,
) *checkoutService {
return &checkoutService{
db: db,
logger: logger,
...,
sfpy: sfpy,
}
}

Service instantiation#

Before initializing this service, you would initialize the sfpy client and pass that in as an argument like so:

// Assuming api key and secret key are passed in to the service
// via environment variables or a config file and are available
sfpy := sfpy.NewClient(apikey, secretkey)
// Assuming db, logger and any other property is already initialized
// and available to use
cs := NewCheckoutService(db, logger, ..., sfpy)

Part 3 - Exposing an endpoint to create a new order#

In a traditional commerce setting, customers add items to a cart, or make a purchase selection with the intention of paying for the good or service. At this point, developers are usually familiar with integrating something like Stripe or PayPal which handles collecting payment information on an externally hosted page, redirects the customer back to the store after a successful payment and notifies the merchant of a successful payment through a webhook so that the system can reconcile the order and mark it paid. This is the exact flow we will cover below.

Assuming you have an API endpoint hosted on your server to initialize an order, you will want to create a payment request on SFPY with details like total amount to charge, any optional discount or any optional tax to add. An overly simplified API contract is presented below:

type CreateOrder struct {
CustomerID string `json:"customer_id"`
ProductID string `json:"product_id"`
Quantity int `json:"quantity"`
}

Decode an incoming HTTP request into this struct

func DecodePostRequest(ctx context.Context, r *http.Request) (interface{}, error) {
var req CreateOrder
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return &req, nil
}

Handle the business logic and API call to SFPY in your service (defined above)

func (cs *checkoutService) CreateOrder(ctx context.Context, request *CreateOrder) (interface{}, error) {
// Do any validations or custom business logic:
// - Verify that the product exists in your database
// - Verify that there is enough inventory to purchase
// - Fetch the product price details
// - etc...
//
// Once you're ready to request payment from the customer you can proceed as follows:
// Create an internal order in your system.
// This is for example purposes but assume
// something like this will happen on your service.
product := cs.db.FetchProduct(ctx, request.ProductID)
order := CreateNewInternalOrder(customer, product, quantity)
// Call SFPY to generate a new payment request
apiResponse, err := cs.sfpy.Endpoints.AddOrder(ctx, &requests.Request{
OrderService: &requests.OrderService{
Order: &requests.Order{
// Replace this with your Ethereum address
Address: "0x742Df1612A701a130c71C9Ce3971Db549917cE29",
// Replace this with your newly created order id
Reference: order.GetID(),
// Depending on whether this a test you can use
// types.RINKEBY,
// types.KOVAN,
// types.GOERLI,
// types.ROPSTEN
ChainID: uint(types.MAINNET),
Cart: &requests.Cart{
// Replace this with your site name
Source: "MyWebsite",
// If the customer cancels the payment, this is the
// url to redirect them back to. SFPY will perform a
// simple GET based redirect
CancelURL: "https://mystore.com/cart",
// When the customer completes the payment, SFPY will
// redirect them back to this URL. This is usually to
// show the customer a success page or order confirmation
// page
CompleteURL: "https://mystore.com/order/<order-id>/complete",
},
PurchaseTotals: &requests.PurchaseTotal{
SubTotal: &requests.PriceMoney{
// Currently we only support USD
Currency: "USD",
// Convert the product price to cents
// if you use dollar amounts
Amount: product.Price * quantity, // in cents
},
// Optionally add a discount
Discount: &requests.PriceMoney{
Currency: "USD",
Amount: 2000, // $20 in cents
},
// Optionally add a tax
TaxTotal: &requests.PriceMoney{
Currency: "USD",
Amount: 1000, // $10 in cents
},
},
},
},
})
if err != nil {
level.Error(as.logger).Log("message", "unable to create order", "error", err.Error())
return nil, err
}
// apiResponse contains a field called `Data` that is a json.RawMessage.
// You need to decode it to get access to the underlying payment request
// object
sfpyOrder := responses.Order{}
if err := json.Unmarshal(response.Data, &sfpyOrder); err != nil {
return nil, err
}
// Now you have access to the SFPY order object. You can store the order
// token in your database against your internal order object to refer to
// it once a payment has been made
order.SetExternalPaymentID(sfpyOrder.Token)
if err := order.Save(ctx); err != nil {
level.Error(as.logger).Log("message", "unable to update internal order", "error", err.Error())
return nil, err
}
// To convert the order object into a link that you can use to redirect the customer to,
// the SFPY client provides a handy function for you.
paymentLink := cs.client.ConstructLink(order.Token)
// You can now return this link in your response and perform a redirect
// on the front end or programmatically perform a redirect server side.
// For simplicity, we will return the payment link to the front end
return &Reponse{ Data: paymentLink }, nil
}

Step 4 - Complete the payment#

Once you've successfully performed the API request, you can return the generated payment link back to the front end. With javascript, after parsing the response, you can redirect the customer to SFPY to complete the payment as follows:

// This is your API call above
const paymentLink = await apiCall();
// Use this to redirect your customer to SFPY
window.location.replace(paymentLink);

Hopefully, if everything goes right, your customer will be redirected to SFPY to complete their payment using one of the 5 currently supported currencies:

  • ETH
  • USDC
  • DAI
  • TETHER
  • WBTC

Customer payments experience#

As an example, this is what your customer would experience.

Make a payment

Conclusion#

Thank you for following along! In the next episode, we'll learn how to handle incoming webhooks in order to programmatically mark the order as PAID and save payment details to your database for future reference.

In the meantime, if you have any questions, please join us on our Discord Server. We're more than happy to help troubleshoot any problems you face.

Announcing SFPY

Prologue#

When we started Safepay in 2019 our mission was, and still is, to enable Pakistani businesses of all sizes to accept digital payments online. We've come a long way since the early days of only accepting credit and debit cards and by working with various financial institutions in the country, we are now able to allow our businesses to process multiple payment methods, both local and international.

But as we delved deeper to understand how money actually moves within the interconnected superhighways of the traditional financial world, we discovered a world of staggering complexity. Overwhelmed, we decided to follow the age old advice when dealing with flows of funds: follow the money.

What we found was pretty eye opening - the current financial industry that caters to merchants wishing to accept payments online suffers from a very major bias. The graphic below is based on a report released by the Federal Reserve Bank of Philadelphia a few years back, but is still very relevant today.

revenue-breakdown-per-merchant-size

The pyramid on the left has merchants who accept credit and debit cards grouped by size. The inverted pyramid in the middle represents the amount of money they process. The pyramid on the right is the net revenue which the credit card industry makes. In short, small businesses pay forty-five times more than larger companies do. Not only that, smaller businesses are forced to accept terms that are designed to be ambigious and in the long term, hinder their growth. The most striking part is that there is a whole group of merchants that are left out, without permission to accept digital payments either because of bad credit, small volumes or lack of interest from acquirers to facilitate them.

While we're still working hard to help businesses in Pakistan accept digital payments online, our allowed jurisdiction is limited geographically. But we knew this problem exists for merchants in other countries as well. That is when we decided to take a more serious look at Ethereum and the blossoming DeFi movement. Ethereum, and distributed ledgers, are unique in many ways, but to us the value lay in two main areas:

  • Permissionless: Anyone can join the network and conduct commerce as and when they choose.
  • Ownership: Participants are in control of their financial wealth and assets as opposed to having a "trusted" third party be custodians of their funds.

The second point is very important because trust is fragile and only remains while it's there. The consequence of losing trust (by either parties) in the context of financial transactions can be quite severe.

So we went down a second rabbit hole, this time entering the world of smart contracts, immutable code, flash loans and tokenomics. The end result is something I'm very happy to share today.

Introducing SFPY#

SFPY is the next iteration of Safepay - a way for businesses to accept payments in digital tokens - that closes the gap we were facing in facilitating small businnesses. We wanted to reimagine how accepting payments would look like from a merchants perspective and ended up flipping the entire model on its head.

To learn more about how SFPY works, please refer to our Getting started guide, but read along for a brief overview if you're interested.

At a high level, SFPY allows merchants to accept digital tokens (ERC-20) and Ethereum as payment currencies from their customers without any fees (gas fees still applicable). Not only can they do so for free, the funds that they receive from customers are transformed into an asset class on which they can actually earn fees as revenue.

This is achieved through liquidity pools that aggregate funds across all merchants using SFPY, and expose an opening for traders to access collateral-free margin through flash loans.

  • To learn how to accept payments as a merchant, you can read our explanation on Payments.
  • To learn how fee earning liquidity pools work, you can read our explanation on Pools

The nice side-effect of building on Ethereum is that we at no point control any funds. Said in another way, merchants are in complete control of their hard earned revenue. At any point in time, they can choose to withdraw their outstanding liquidity to their own wallets without any complicated settlement schedules, additional fees and most of all permission.

Our aim is to provide merchants with a similar experience of accepting payments that they would be used to in terms of plugins for various e-commerce platforms, APIs for custom integrations, SDKs for simplifying the integration process and an open source interface to manage their payments and refunds from.

Apps#

Flash Loans are very easily accessible through a neat interface:

function borrow(address sender, uint256 amount, bytes calldata data)

More details can be found in the Flash Loans guide and Borrower Reference.

The concept of "triggering" a smart contract by conforming to an interface led us to think about a new interface with a different use case. What if you could trigger an entire suite of smart contracts after a payment has been made? Developers that are interested in selling items online (NFTs, subscriptions, physical goods etc), raising funds through ICOs or even things we haven't thought about yet, can all do so now without having to worry about the payment acceptance side. This is now neatly encapsulated in the afterPay callback:

function afterPay(address sender, address token, uint256 amount, bytes calldata data) external

The basic idea is as follows: After a customer has transferred the required tokens into the pool but before any liquidity tokens are minted for the merchant, the smart contract triggers the function described above. This calls the developer's smart contracts through which they can decide whether the customer has paid enough, paid with an acceptable token and any additional business logic they deem necessary before transfering the purchased item to the customer. Most importantly, the developer can revert the entire transaction at any point returning all funds back to the customer if they choose to abort the transaction.

Flash Apps are very early in development and quite frankly are risky at the moment. Customers interacting with Flash Apps, should be absolutely certain that they trust the code written before making a payment since any nefarious actor can take the funds without actually honoring the purchase (although this problem exists in traditional finance as well).

However, we see Flash Apps evolving into a marketplace or App Store of sorts where the community hand picks their favorite apps and lists them under a common standard. This way, customers have more confidence in picking the apps they wish to interact with.

While this is still in early development, you can read more about how to use Flash Apps over here.

Epilogue#

We're very interested with working with developers, businesses, and the wider community in general to build the future of payments on Web 3.0. While it's still early, SFPY is available to use today in a beta phase. We would appreciate any feedback you have!