Skip to content
Snippets Groups Projects
Commit 244d12d3 authored by Johan de Klerk's avatar Johan de Klerk
Browse files

Get Absa CIB statement transactions

parent 6fa1ee62
Branches
Tags
No related merge requests found
package bank_transactions
import (
"crypto/tls"
"encoding/base64"
"github.com/go-resty/resty/v2"
"github.com/google/uuid"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/errors"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/secrets_manager"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/string_utils"
"gitlab.bob.co.za/bob-public-utils/bobgroup-go-utils/struct_utils"
"io"
"strings"
"time"
)
type AbsaLoginInfo struct {
CertExpiry string
CertSerialNumber string
PartialNo string
DeviceFingerprint string
AbsaSecretName string
IsDebug bool
}
const (
sleepTime = time.Second * 2
)
func GetAbsaTransactions(loginInfo AbsaLoginInfo) ([]byte, error) {
client, err := setupAbsaClient(loginInfo.AbsaSecretName, loginInfo.IsDebug)
if err != nil {
return nil, err
}
defer logoff(client)
// Get Initial session ID
err = login(client, loginInfo)
if err != nil {
return nil, err
}
// Navigate to statement download
err = startStatementDownloadProcess(client)
if err != nil {
return nil, err
}
// Request statement
dateString := time.Now().Format("20060102")
_, err = statementDownloadProcessCall(client, map[string]string{
"page": "enquiry-statement",
"groupselect": "",
"linkallterm": "false",
"buttonClicked": "proceed",
"SelType": "D",
"FAInd": "false",
"systemDate": dateString,
"account_options": "all",
"fromaccountgroup": "blank",
"fromaccount": "blank",
"fromaccountdisplay1": "blank",
"showall_option": "showall",
"format_options": "file",
"file_format_options": "csv",
"statement_options": "last",
"days": "7",
"startdate": dateString,
"enddate": dateString,
"startnum": "",
"endnum": "",
"ErrorText": "",
})
if err != nil {
return nil, err
}
// Wait for statement to process
for {
responseBytes, err := statementDownloadProcessCall(client, map[string]string{
"page": "enquiry-statement-status",
"buttonClicked": "proceed",
"ErrorText": "",
})
if err != nil {
return nil, err
}
if strings.Contains(string(responseBytes), "The statement file is available for download") {
break
}
time.Sleep(sleepTime)
}
// Download file
statementBytes, err := statementDownloadProcessCall(client, map[string]string{
"page": "enquiry-statement-file",
"buttonClicked": "saveas",
"filename": "25591016.csv",
"ErrorText": "",
})
if err != nil {
return nil, err
}
return statementBytes, nil
}
func setupAbsaClient(secretName string, isDebug bool) (*resty.Client, error) {
// Get cert data
credentials, err := getAbsaSecret(secretName, isDebug)
if err != nil {
return nil, err
}
certDataString := credentials["cert"]
certData, _ := base64.StdEncoding.DecodeString(certDataString)
privateKeyDataString := credentials["private_key"]
privateKeyData, _ := base64.StdEncoding.DecodeString(privateKeyDataString)
cert, err := tls.X509KeyPair(certData, privateKeyData)
if err != nil {
return nil, err
}
client := resty.New().
SetCertificates(cert)
return client, nil
}
func login(client *resty.Client, loginInfo AbsaLoginInfo) error {
// Get password
credentials, err := getAbsaSecret(loginInfo.AbsaSecretName, loginInfo.IsDebug)
if err != nil {
return err
}
username := credentials["username"]
password := credentials["password"]
// Get Initial session ID
response, err := client.R().
SetDoNotParseResponse(true).
Get("https://bionline.absa.co.za/businessintegrator/login_new.jsp")
if err != nil {
return err
}
time.Sleep(sleepTime)
// Login
universalTracker := uuid.New().String() + "-" + uuid.New().String()
response, err = client.R().
SetDoNotParseResponse(true).
SetFormData(map[string]string{
"lastpage": "login",
"buttonClicked": "proceed",
"hasCert": "Y",
"certExpired": "false",
"certExpiry": loginInfo.CertExpiry,
"displayIgnoreButton": "true",
"partialNo": loginInfo.PartialNo,
"universalTracker": universalTracker,
"deviceFingerprint": loginInfo.DeviceFingerprint,
"certSerialNo": loginInfo.CertSerialNumber,
"UserID": username,
"password_one": password,
}).
Post("https://bionline.absa.co.za/businessintegrator/SignOn")
if err != nil {
return err
}
responseBytes, err := io.ReadAll(response.RawResponse.Body)
if strings.Contains(string(responseBytes), "User already signed on") {
// Log out
response, err = client.R().
SetDoNotParseResponse(true).
SetFormData(map[string]string{
"buttonClicked": "proceed",
"lastpage": "signedon_to_signoff",
"ErrorText": "",
}).
Post("https://bionline.absa.co.za/businessintegrator/SignOn")
if err != nil {
return err
}
// Login again
err := login(client, loginInfo)
if err != nil {
return err
}
}
return nil
}
func startStatementDownloadProcess(client *resty.Client) error {
// Navigate to statement download
response, err := client.R().
SetDoNotParseResponse(true).
SetQueryParam("dojo.preventCache", string_utils.Int64ToString(time.Now().UnixMilli())).
SetHeader("Referer", "https://bionline.absa.co.za/businessintegrator/enquiries/menu.jsp").
SetHeader("Content-Type", "application/x-www-form-urlencoded").
Get("https://bionline.absa.co.za/businessintegrator/EnquireBankStatement")
if err != nil {
return err
}
responseBytes, _ := io.ReadAll(response.RawResponse.Body)
// Ensure we are on the "Statement Enquiry" screen
if !strings.Contains(string(responseBytes), "Statement Enquiry") {
return errors.New("Could not load 'Statement Enquiry' screen")
}
time.Sleep(sleepTime)
return nil
}
func statementDownloadProcessCall(client *resty.Client, formData map[string]string) ([]byte, error) {
response, err := client.R().
SetDoNotParseResponse(true).
SetFormData(formData).
Post("https://bionline.absa.co.za/businessintegrator/EnquireBankStatement")
if err != nil {
return nil, err
}
responseBytes, err := io.ReadAll(response.RawResponse.Body)
time.Sleep(sleepTime)
return responseBytes, nil
}
func logoff(client *resty.Client) error {
// Logoff
_, err := client.R().
SetDoNotParseResponse(true).
Get("https://bionline.absa.co.za/businessintegrator/Logoff")
if err != nil {
return err
}
return nil
}
func getAbsaSecret(secretName string, isDebug bool) (map[string]string, error) {
// This secret is manually created on Secrets Manager
/*
Extract the key and cert from the p12 certificate
```
openssl pkcs12 -in Absa.p12 -nocerts -out userkey.pem -nodes
openssl pkcs12 -in Absa.p12 -clcerts -nokeys -out usercert.pem
```
Base64 encode the key and cert and save it in secrets manager
*/
secretJson, _ := secrets_manager.GetSecret(secretName, isDebug)
var credentials map[string]string
err := struct_utils.UnmarshalJSON([]byte(secretJson), &credentials)
if err != nil {
return nil, err
}
return credentials, nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment