Deploying a Scheduling Service for Automated Payments
The tutorial can be used to learn how to automate a scheduled employee payment system on the Algorand Network. With an automated or scheduled payment system, you do not need to stress yourself over making payments to staff/employees manually. With this tutorial you will learn how to:
- Save time and resources
- Make stress-free task delegation
- Use scheduling for prioritization of tasks
Requirements
Background
This is a Payment Scheduler DApp using the C#/.NET framework, Algorand Package/SDK, alongside Hangfire to process automated payments on the Algorand Network.
Additional Resources:
-
Learn how to develop a Desktop DApp that can create/generate an account on the Algorand Network
-
Learn how to develop a Mobile DApp to create account addresses and keys
Background Tasks Made Easy With Hangfire And .Net 5
Alert
This code uses account restore from a private key, this is not recommended for production use, and is provided for only for tutorial purposes. To integrate a web application use a wallet SDK to sign and send transactions, such as the AlgoSigner SDK for Chrome Extension. See tutorial here. Replace SRC_ACCOUNT With your passphrase from creating an account, the quickest way to do this is see example here, or create visually with AlgoDEA IDE here.
Steps
- 1. Setup, Configure, and Create Application
- 2. Install Packages/SDKs
- 3. Configure Services
- 4. Configure/Setup Database
- 5. Configure/Setup the Startup class
- 6. Configure/Setup the appsettings.json file
- 7. Fund Account on Testnet
- 8. Run the application
- 9. Download Complete Source Code
- 10. View Complete Tutorial Video
1. Setup, Configure, and Create Application
Open Visual Studio, Create and set up project template with .Net Core 3.1 as shown below in Fig 1-1
Select the ASP.NET Core Web API project template and proceed to create the project.
Figure 1-1 Create Project
Name the Project “AlgorandAutomatedPayment” or give it a preferred name of your choice.
Configure the project target framework under Additional Information Window with the selected/checked options as shown in Fig 1-2 below:
Figure 1-2 Configure Project Information
2. Install Packages/SDKs
In order to configure Hangfire and Algorand payment system, we need to install Hangfire related packages and Algorand package as shown below in Fig 2-1.
Figure 2-1 Install Project Packages
Figure 2-2 Install Project Packages
3. Configure Services
Proceed to Configure the services that would be used to process the payment functionalities.
Add a Services folder and add a new interface, IPayments.cs
and a class Payments.cs
Figure 3-1 Create Project Services, IPayment Interface and PaymentService Class
IPayments.cs
Interface
using System.Threading.Tasks;
namespace AlgorandAutomatedPayment.Services
{
public interface IPayments
{
Task OneTimePayment();
Task GroupedPayments();
}
}
We declare the related interfaces for both the One Time Payment and Grouped payments in the code snippet above.
Payments.cs
Class
This will implement the IPayment Interface and process the payment functionalities for both Atomic and One Time payments on the Algorand Network.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Algorand;
using Account = Algorand.Account;
using Algorand.V2;
using Algorand.Client;
using Algorand.V2.Model;
namespace AlgorandAutomatedPayment.Services
{
public class Payments : IPayments
{
public async Task GroupedPayments()
{
string ALGOD_API_ADDR = "https://testnet-algorand.api.purestake.io/ps2"; //find in algod.net
string ALGOD_API_TOKEN = "B3SU4KcVKi94Jap2VXkK83xx38bsv95K5UZm2lab"; //find in algod.token
string SRC_ACCOUNT = "table worth young female soft alert example shock pepper bind peanut amazing omit claw escape agree turn people symbol keen tooth cancel submit abstract fantasy";
string DEST_ADDR = "VTQPQJTPTPER6WENW5Q4HEFXOVOGPAYWXJGBE7ECSME2NUYPYVIDMOP5I4";
string DEST_ADDR2 = "TMY2LDQTLDAKZXORR5OKOKT57PIAPACEXTSLQNEIFDCSC3YTNXXY4REFQA";
Account src = new Account(SRC_ACCOUNT);
var algodApiInstance = new AlgodApi(ALGOD_API_ADDR, ALGOD_API_TOKEN);
Algorand.V2.Model.TransactionParametersResponse transParams;
try
{
transParams = algodApiInstance.TransactionParams();
}
catch (ApiException e)
{
throw new Exception("Could not get params", e);
}
// let's create a transaction group
var amount = Utils.AlgosToMicroalgos(1);
var tx = Utils.GetPaymentTransaction(src.Address, new Address(DEST_ADDR), amount, "pay message", transParams);
var tx2 = Utils.GetPaymentTransaction(src.Address, new Address(DEST_ADDR2), amount, "pay message", transParams);
//SignedTransaction signedTx2 = src.SignTransactionWithFeePerByte(tx2, feePerByte);
Digest gid = TxGroup.ComputeGroupID(new Algorand.Transaction[] { tx, tx2 });
tx.AssignGroupID(gid);
tx2.AssignGroupID(gid);
// already updated the groupid, sign
var signedTx = src.SignTransaction(tx);
var signedTx2 = src.SignTransaction(tx2);
try
{
//contact the signed msgpack
List<byte> byteList = new List<byte>(Algorand.Encoder.EncodeToMsgPack(signedTx));
byteList.AddRange(Algorand.Encoder.EncodeToMsgPack(signedTx2));
var id = algodApiInstance.RawTransaction(byteList.ToArray());
Console.WriteLine("Successfully sent tx group with first tx id: " + id);
Console.WriteLine("Confirmed Round is: " +
Utils.WaitTransactionToComplete(algodApiInstance, id.TxId).ConfirmedRound);
}
catch (ApiException e)
{
// This is generally expected, but should give us an informative error message.
Console.WriteLine("Exception when calling algod#rawTransaction: " + e.Message);
}
}
public async Task OneTimePayment()
{
string ALGOD_API_ADDR = "https://testnet-algorand.api.purestake.io/ps2"; //find in algod.net
string ALGOD_API_TOKEN = "B3SU4KcVKi94Jap2VXkK83xx38bsv95K5UZm2lab"; //find in algod.token
string SRC_ACCOUNT = "table worth young female soft alert example shock pepper bind peanut amazing omit claw escape agree turn people symbol keen tooth cancel submit abstract fantasy";
string DEST_ADDR = "VTQPQJTPTPER6WENW5Q4HEFXOVOGPAYWXJGBE7ECSME2NUYPYVIDMOP5I4";
Account src = new Account(SRC_ACCOUNT);
AlgodApi algodApiInstance = new AlgodApi(ALGOD_API_ADDR, ALGOD_API_TOKEN);
try
{
var trans = algodApiInstance.TransactionParams();
}
catch (ApiException e)
{
Console.WriteLine("Exception when calling algod#getSupply:" + e.Message);
}
TransactionParametersResponse transParams;
try
{
transParams = algodApiInstance.TransactionParams();
}
catch (ApiException e)
{
throw new Exception("Could not get params", e);
}
var amountsent = Utils.AlgosToMicroalgos(2);
var tx = Utils.GetPaymentTransaction(src.Address, new Address(DEST_ADDR), amountsent, "pay message", transParams);
var signedTx = src.SignTransaction(tx);
Console.WriteLine("Signed transaction with txid: " + signedTx.transactionID);
// send the transaction to the network
try
{
var id = Utils.SubmitTransaction(algodApiInstance, signedTx);
Console.WriteLine("Successfully sent tx with id: " + id.TxId);
Console.WriteLine(Utils.WaitTransactionToComplete(algodApiInstance, id.TxId));
}
catch (ApiException e)
{
// This is generally expected, but should give us an informative error message.
Console.WriteLine("Exception when calling algod#rawTransaction: " + e.Message);
}
}
}
}
The PaymentService
class will implement the functionalities of both the Grouped Transactions and One Time Payment.
The GroupedPayments
method handles the processing of the grouped/atomic transactions on the Algorand Network, while the OneTimePayment
will process the One Time Payment which will be scheduled/automated using the Hangfire package.
Note:
Note this code uses account restore from a private key, this is not recommended for production use, and is provided for only for tutorial purposes. To integrate a web application use a wallet SDK to sign and send transactions, such as the AlgoSigner SDK for Chrome Extension. See tutorial here .
Replace SRC_ACCOUNT With your passphrase from creating an account, the quickest way to do this is see example here
Or create visually with AlgoDEA IDE.
Or create from the Algorand Wallet (see App store or Google play) you can change the setting to TestNet, and create a new account and copy off the passphrase.
Now, create a Job.cs
class in the Project solution as shown below:
Figure 3-2 Create a class called Job.cs
The Job.cs
class will implement the Payment.cs
functionalities which would be used for automated transactions.
Job.cs
Class
using AutomatedPaymentAlgorand.Services;
using System.Threading.Tasks;
namespace AutomatedPaymentAlgorand
{
public class Job
{
private readonly IPaymentService _payments;
public Job(IPaymentService payments)
{
_payments = payments;
}
public Task OneTimePaymentJobAsync()
{
var result = _payments.OneTimePayment();
return result;
}
public Task GroupedPaymentsJobAsnyc()
{
var result = _payments.GroupedPayments();
return result;
}
}
}
4. Configure/Setup Database
In this step, we will Setup the database that will work with Hangfire.
Hangfire has an option to store all the job-related information in a database. For this, we just need to create a database.
Follow the steps as shown below to create/setup a database.
Open the Server Explorer tab and click on the database icon as shown in Fig 4-1 below to connect to a database.
Figure 4-1 Open Server Explorer to Setup/Create Database
Proceed to setup the database following the steps in Figure 4-2 below
Fill in the server name as shown below as shown in Figure 4-2 if you are using the in-built MSSQL database with Visual Studio.
Select or Enter the database name you want to use for the project and create the database.
Figure 4-2 Setup Database Name and Information
Click Yes to proceed as shown in Fig 4-3. This will create the database.
Figure 4-3 Create Database
5. Configure/Setup the Startup class
In this step, we will Configure the connection string, Hangfire, services injection in the startup file and also process the background tasks with Hangfire.
Startup.cs
Class
using AutomatedPaymentAlgorand.Services;
using Hangfire;
using HangfireBasicAuthenticationFilter;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace AutomatedPaymentAlgorand
{
public class Startup
{
private static IPaymentService paymentService;
private readonly Job jobscheduler = new Job(paymentService);
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "AutomatedPaymentAlgorand", Version = "v1" });
});
#region Configure Hangfire
services.AddHangfire(c => c.UseSqlServerStorage(Configuration.GetConnectionString("myconn")));
GlobalConfiguration.Configuration.UseSqlServerStorage(Configuration.GetConnectionString("myconn")).WithJobExpirationTimeout(TimeSpan.FromDays(7));
#endregion
#region Services Injection
services.AddTransient<IPaymentService, PaymentService>();
#endregion
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IBackgroundJobClient backgroundJobClient, IRecurringJobManager recurringJobManager)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "AutomatedPaymentAlgorand v1"));
}
#region Configure Hangfire
app.UseHangfireServer();
//Basic Authentication added to access the Hangfire Dashboard
app.UseHangfireDashboard("/hangfire", new DashboardOptions()
{
AppPath = null,
DashboardTitle = "Hangfire Dashboard",
Authorization = new[]{
new HangfireCustomBasicAuthenticationFilter{
User = Configuration.GetSection("HangfireCredentials:UserName").Value,
Pass = Configuration.GetSection("HangfireCredentials:Password").Value
}
},
//Authorization = new[] { new DashboardNoAuthorizationFilter() },
//IgnoreAntiforgeryToken = true
}); ;
#endregion
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
#region Job Scheduling Tasks
// Recurring One Time Payment Job for every 5 min
recurringJobManager.AddOrUpdate("Insert Tasks : Runs Every 5 Min", () => jobscheduler.OneTimePaymentJobAsync(), "*/5 * * * *");
// Recurring Atomic Transfer Job for every 5 min
recurringJobManager.AddOrUpdate("Insert Tasks : Runs Every 5 Min", () => jobscheduler.GroupedPaymentsJobAsnyc(), "*/5 * * * *");
//Fire and forget job
var jobId = backgroundJobClient.Enqueue(() => jobscheduler.OneTimePaymentJobAsync());
//Schedule Job / Delayed Job
backgroundJobClient.Schedule(() => jobscheduler.GroupedPaymentsJobAsnyc(), TimeSpan.FromDays(29));
#endregion
}
}
}
This will process the necessary tasks needed for our automated transactions on the Algorand Network.
There are 4 types of jobs that mostly we will use. We have created all 4 jobs in a startup.cs
file.
- Recurring job - Every 5 minutes the background task runs and processes payments.
- Fire and Forget - This job runs only once when we run the application.
- Schedule Job - If you want to schedule a task to run at a particular time.
6. Configure/Setup the appsettings.json
file
In this step, we will setup our appsettings.json
file to configure our hangfire dashboard (username and password) for authentication and our database configuration Connection string.
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ConnectionStrings": {
"myconn": "Server=(localdb)\\mssqllocaldb;Database=Hangfiretest.Dev;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"AllowedHosts": "*",
"HangfireCredentials": {
"UserName": "admin",
"Password": "admin@123"
}
}
To Secure the hangfire dashboard we setup login authentication in order to access the hangfire dashboard. We have hardcoded the username and password in the appsettings.js
file to consume those in the startup.cs
as shown in step 4.
7. Fund Account on Testnet
The following tutorial is for demo purposes and is being worked on the TestNet, no real transactions will be made.
Visit the: TestNet Bank to fund the account.
Check the “I am not a Robot box”, paste in your account address (master account address used to fund other accounts) in the field below as shown in Figure 7-1:
Figure 7-1 Fund the Accounts Using Algorand Dispenser
Visit https://testnet.algoexplorer.io/
As shown in Fig 6-2 below, search for your account details via the Search bar and make sure it is running on Testnet.
This is to keep track of ongoing transactions as we run the application.
Figure 7-2 Algo Explorer Dashboard
8. Run the application
By default, the swagger endpoint will open.
Now type hangfire in the URL by removing the swagger. It will ask for a username and password as we had already set up the authentication mechanism.
Figure 8-1 Hangifre Dashboard Authentication
If you click on jobs and succeed then we will see the job execution status and its time, and also we can see the historical graph in the dashboard as well.
Figure 8-2 Hangifre Dashboard
Figure 8-3 Hangifre Dashboard
9. Download Complete Source Code
Source Code can be found on GitHub
10. View Complete Tutorial Video