How to add Payments to your Spring boot Application with Flutterwave V3

Adeogo Oladipo
5 min readOct 25, 2020

--

Flutterwave is an application program interface that lets you process credit card, and local alternative payment, like mobile money and ACH(Automated Clearing House) across Africa.

In the following tutorial, I’ll be showing you how to add Rave to your Springboot app to receive payments from users through the various methods supported by Flutterwave.

Setup

Dependencies

Here is the dependencies section of the pom.xml file

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20190722</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.6</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

Properties

You’ll need to add two properties to your application.properties or application.yml file.

flutterwave.public-key=FLWPUBK_*******************************-X
flutterwave.secret-key=FLWSECK_*******************************-X

Storing them here instead of your Java code allows for security and flexibility goals.

Data Classes

You will need to create two classes, one for the Payload and the other for the Customer.

Here is the content of Customer.java

public class Customer implements Serializable {
@Expose
private String name;
@Expose
private String email;
@Expose
private String phone_number;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getPhone_number() {
return phone_number;
}

public void setPhone_number(String phone_number) {
this.phone_number = phone_number;
}
}

and Payload.java

public class Payload implements Serializable {
@Expose
private String amount;
@Expose
private String currency;
@Expose
private String country;
@Expose
private String description;
@Expose
private String payment_method;
@Expose
private String public_key;
@Expose
private String tx_ref;
@Expose
private String redirect_url;
@Expose
private Customer customer;

public Payload() {
customer = new Customer();
}

public String getAmount() {
return amount;
}

public void setAmount(String amount) {
this.amount = amount;
}

public String getCurrency() {
return currency;
}

public void setCurrency(String currency) {
this.currency = currency;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getPayment_method() {
return payment_method;
}

public void setPayment_method(String payment_method) {
this.payment_method = payment_method;
}

public void setEmail(String email) {
customer.setEmail(email);
}

public void setName(String name) {
customer.setName(name);
}

public void setPhonenumber(String phonenumber) {
customer.setPhone_number(phonenumber);
}

public String getPublic_key() {
return public_key;
}

public void setPublic_key(String public_key) {
this.public_key = public_key;
}

public String getTx_ref() {
return tx_ref;
}

public void setTx_ref(String tx_ref) {
this.tx_ref = tx_ref;
}

public String getRedirect_url() {
return redirect_url;
}

public void setRedirect_url(String redirect_url) {
this.redirect_url = redirect_url;
}

public Customer getCustomer() {
return customer;
}

public void setCustomer(Customer customer) {
this.customer = customer;
}

@Override
public String toString() {
Gson gson = new GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.create();
return gson.toJson(this);
}
}

Templates

The resources/templates folder will contain the View part of our MVC architecture.

Create a file called pay.html with the content as defined below.

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<body>
<center>
Processing... <br/> <img style="height: 50px;" src="https://media.giphy.com/media/swhRkVYLJDrCE/giphy.gif"/>
</center>
<script type="text/javascript" th:src="${url}">
</script>
<script>
document.addEventListener("DOMContentLoaded", function (event) {
let payload = '[[${payload}]]';
payload = payload.replaceAll(/&quot;/g, '\\"');
payload = payload.replaceAll('\\', '');
var data = JSON.parse(payload);
FlutterwaveCheckout(data);
});
</script>
</body>
</html>

Also create a file called callback.html for the /callback endpoint.

<html>
<body>
Response
</body>
</html>

The Controller class

The controller acts as an entry point for our request. We will define two endpoint, /pay and /callback. /pay will be call to initialize the payment and for cancellations, while /callback will be called by the flutterwave inline plugin when your customer completes the payment.

if you specify a callback function in your request payload when your customer completes the payment, we will call the function and pass the payment details to you. https://developer.flutterwave.com/docs/flutterwave-inline

/pay

@RequestMapping("/pay")
public String pay(@ModelAttribute Payload payload, Model model, HttpServletRequest request) throws Exception {
if (request.getMethod().contentEquals("GET")) {
//This happens in case of a cancelling. This redirects to the home page.
return "redirect:";
}
paymentService.initialize(model);
return "pay";
}

/callback

@GetMapping("/callback")
public String callback(@RequestParam Map<String, String> params) {
String transactionId = params.get("transaction_id");
Map<String, Object> transactionData = paymentService.verifyTransaction(transactionId);
return "callback";
}

The Service class

The Service class handles the bulk of the logic and data manipulation of the implementation.

Variables

We will add two global variables to represent our keys.

@Value("${flutterwave.public-key}")
private String ravePublicKey;
@Value("${flutterwave.secret-key}")
private String raveSecretKey;

Methods

Next, we will define our initialize method, this method prepares the payload for the flutterwave inline plugin.

public void initialize(Model model) throws Exception {
model.addAttribute("url", "https://checkout.flutterwave.com/v3.js");
Payload payload = (Payload) model.getAttribute("payload");
if (payload == null){
throw new Exception("Payload is empty");
}
addRaveDetailsToPayload(payload);
model.addAttribute("payload", payload);
}

It get’s the body of the post request and creates the final payload for the flutterwave inline plugin.

then we declare addRaveDetailsToPayload method.

private void addRaveDetailsToPayload(Payload payload) {
String txRef = UUID.randomUUID().toString();
payload.setPublic_key(ravePublicKey);
payload.setTx_ref(txRef);
payload.setRedirect_url("/callback");
}

then we declare the verifyTransaction method.

public Map<String, Object> verifyTransaction(String transactionId) {
String url = "https://api.flutterwave.com/v3/transactions/" + transactionId + "/verify";
RestTemplate rest = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth(raveSecretKey);
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
ParameterizedTypeReference<Map<String, Object>> typeRef = new ParameterizedTypeReference<>() {
};
ResponseEntity<Map<String, Object>> response = rest.exchange(
url, HttpMethod.GET, httpEntity, typeRef);
return response.getBody();
}

As advised by flutterwave, always verify a transaction’s status before prceeding to deliver goods or service. The method uses Spring Boot’s RestTemplate to make a REST call to the flutterwave verify endpoint. Based on the response, you can proceed to either deny service or grant service.

Form

Here is an example file for the POST request to the /pay endpoint.

<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<body>
<h1>HOME</h1>
<form th:action="@{/pay}" th:object="${payload}" method="post">
<div class="form-row">
<input type="hidden" name="amount" value="500"/> <!-- Replace the value with your transaction amount -->
<input type="hidden" name="payment_method" value="card,mobilemoney,ussd"/> <!-- Can be card, account, both -->
<input type="hidden" name="description" value="End Police Brutality"/>
<input type="hidden" name="country" value="NG"/> <!-- Replace the value with your transaction country -->
<input type="hidden" name="currency" value="UGX"/> <!-- Replace the value with your transaction currency -->
<input type="hidden" name="email" value="end@sars.com"/> <!-- Replace the value with your customer email -->
<input type="hidden" name="firstname" value="End" /> <!-- Replace the value with your customer firstname -->
<input type="hidden" name="lastname" value="Sars"/> <!-- Replace the value with your customer lastname -->
<input type="hidden" name="phonenumber" value="21102020"/>
</div>
<input type="submit" value="Buy"/>-->
</form>
</body>
</html>

Note: The full project can be found on github.

--

--

Adeogo Oladipo
Adeogo Oladipo

Written by Adeogo Oladipo

Co-Founder and CTO @DokitariUG. A Strong believer in the Potential in Each Human.

No responses yet