Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance Chapa Client with Retrofit2 Integration, Comprehensive Testing, and Resilience Features #1

Open
wants to merge 33 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
4836f18
Change http client to retrofit
amenski Jun 7, 2023
6ecefa1
Add client interface for better extension
amenski Jun 8, 2023
3145b71
small refactor and add http client test
amenski Jun 9, 2023
b40302f
repackage
amenski Jun 9, 2023
9b822e8
Merge pull request #1 from amenski/change-http-client-to-retrofit
amenski Jun 9, 2023
b52614b
test LocalDateTimeDeserializer
amenski Jun 10, 2023
845c40f
add unit test for ChapaClient
amenski Jun 10, 2023
76af5e2
Merge pull request #2 from amenski/client-test
amenski Jun 10, 2023
ab4c9c3
small refactor
amenski Jun 10, 2023
e50f908
add ChapaBuilder for ease of use
amenski Jun 10, 2023
0d4ea70
small refactor
amenski Jun 11, 2023
af3a352
complete unit test with test for main class
amenski Jun 11, 2023
67b8d7f
Merge pull request #3 from amenski/chapa-builder
amenski Jun 11, 2023
5764a33
update documentation
amenski Jun 11, 2023
8bf1900
fix wrong bank object and add example
amenski Jun 11, 2023
22bb63d
modify banks and SubAccountResponse handling
amenski Jun 12, 2023
a29226c
modify InitializeResponseData handling
amenski Jun 12, 2023
1352572
update VerifyResponse call and small refactor
amenski Jun 12, 2023
80a2dd7
modify README.md and refactor Util class
amenski Jun 12, 2023
05ce5c2
fix typo
amenski Jun 12, 2023
102211d
additional documentation
amenski Jun 13, 2023
ea6e5ce
add javadoc generated documentation
amenski Jun 13, 2023
8203144
add javadoc generated documentation
amenski Jun 13, 2023
003b9db
Add retrofit default and retrier builders
amenski Dec 9, 2023
ade0887
repackage classes
amenski Feb 4, 2024
062aade
fix chapa client test
amenski Feb 4, 2024
fee2d12
change groupId + update README
amenski Feb 4, 2024
d06d73b
Merge branch 'main' into retrofit-builder-provider
amenski Feb 4, 2024
f3a9995
merge back from main
amenski Feb 4, 2024
1465776
add retry capability
amenski Feb 4, 2024
dc79d7b
update readme
amenski Feb 4, 2024
49e2330
update java doc
amenski Feb 5, 2024
283cefd
Merge branch 'main' of https://github.com/yaphet17/chapa-java
amenski Feb 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
226 changes: 135 additions & 91 deletions README.md
Expand Up @@ -6,77 +6,109 @@
`-----' `--' `--' `--`--' | |-' `--`--' `-----' `--`--' `--' `--`--'
```

[![BUILD](https://github.com/yaphet17/chapa-java/actions/workflows/maven.yml/badge.svg)](https://github.com/yaphet17/chapa-java/actions/workflows/maven.yml/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.yaphet17/Chapa/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.yaphet17/Chapa) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Unofficial Java package for Chapa Payment Gateway.

Unofficial Java SDK for Chapa Payment Gateway.

## What's new in this version
- You no longer need to deal with `JSON` or `Map<String, Object>` responses. You can just treat response data as a Java object using specific response classes for each request type (e.g. payment initialization, payment verification).
- Better exception handling. The SDK will throw the newly added `ChapaException` on failed requests to Chapa API.
- Bug fixes and design improvements.
- Well-tested and documented code. Check out the Javadoc [here](https://yaphet17.github.io/chapa-java/).
## Features
- You can now implement `IChapaClient` interface and create your own custom implementation
to use your favorite HTTP client.
- Includes split payment feature added by Chapa. You can now get list of supported banks, create
sub-account and perform a split payment. See [Split Payment](https://developer.chapa.co/docs/split-payment/) documentation for more details.
- Additional utility methods to help you to generate a convenient token for your transactions, to map json string
to `PostData` object etc.

## Table of Contents
1. [Documentation](#documentation)
2. [Installation](#installation)
3. [Usage](#usage)
4. [Javadoc](https://yaphet17.github.io/chapa-java/)
5. [Contribution](#contribution)
6. [Example](#example)
7. [License](#license)
4. [Contribution](#contribution)
5. [Example](#example)
6. [License](#license)

## Documentation
Visit official [Chapa's API Documentation](https://developer.chapa.co/docs)
## Installation
Add the below maven dependency to your `pom.xml` file.
```xml
<dependency>
<groupId>io.github.yaphet17</groupId>
<artifactId>Chapa</artifactId>
<version>1.2.2</version>
<groupId>com.github.amenski</groupId>
<artifactId>chapa</artifactId>
<version>1.0.0</version>
</dependency>
```
Or add the below gradle dependency to your `build.gradle` file.
```groovy
implementation 'io.github.yaphet17:Chapa:1.2.2'
implementation 'com.github.amenski:chapa:1.0.0'
```

## Usage

> **Note** : This doc might not fully cover chapa-api. Please refer to the chapa developer doc for more. And contributions are welcome too. Thanks.


Instantiate a `Chapa` class.
```java
Chapa chapa = new Chapa("your-secrete-key");
Chapa chapa = new Chapa.ChapaBuilder()
.client(new ChapaClient()) // --> default implementation
.secretKey("secret-key")
.build();
```
Or if you want to use your own implementation of `ChapaClient` interface.
Or if you want to use your own implementation, implement the methods from `IChapaClient` interface.
```java
Chapa chapa = new Chapa("your-secrete-key", new MyCustomChapaClient());
```
Note: `MyCustomChapaClient` must implement `ChapaClient` interface.
import com.github.amenski.client.ChapaClient;

public class MyCustomChapaClient implements IChapaClient {
...
}


To initialize a transaction, you can specify your information by either using our `PostData` class.
Chapa chapa = new Chapa(new MyCustomChapaClient(), "secrete-key");
```

Note: Starting from version 1.1.0 you have to specify customization fields as a `Map<String, String>` object.
## Retries (with ExponentialBackoff)
To add retries to clients, use `RetrierRetrofitClientProvider`. This class allows configuring 3 retries if a call fails.
Provides retrofit client to make calls to chapa api. <br>
Retry will be done with an <a href="https://en.wikipedia.org/wiki/Exponential_backoff">ExponentialBackoff</a> strategy.<br>
<pre>
Example usage:
public class CustomChapaClient implements IChapaClient {
private String baseUrl = "https://api.chapa.co/v1/";
private ChapaClientApi chapaClientApi;
.
.
private void buildApiClient() {
if (isBlank(baseUrl)) throw new ChapaException("Unable to create a client. Api baseUrl can't be empty");
chapaClientApi = new RetrierRetrofitClientProvider().buildClient(ChapaClientApi.class, baseUrl);
}
}
</pre>


To initialize a transaction, you simply need to specify your information by either using our `PostData` class.

```java
Customization customization = new Customization()
.setTitle("E-commerce")
.setDescription("It is time to pay")
.setLogo("https://mylogo.com/log.png");
.setTitle("E-commerce")
.setDescription("It is time to pay")
.setLogo("https://mylogo.com/log.png");
PostData postData = new PostData()
.setAmount(new BigDecimal("100"))
.setCurrency("ETB")
.setFirstName("Abebe")
.setLastName("Bikila")
.setEmail("abebe@bikila")
.setTxRef(Util.generateToken())
.setCallbackUrl("https://chapa.co")
.setReturnUrl("https://chapa.co")
.setSubAccountId("testSubAccountId")
.setCustomization(customization);
.setAmount(new BigDecimal("100"))
.setCurrency("ETB")
.setFirstName("Abebe")
.setLastName("Bikila")
.setEmail("abebe@bikila.com")
.setTxRef(UUID.randomUUID().toString())
.setCallbackUrl("https://chapa.co")
.setReturnUrl("https://chapa.co")
.setSubAccountId("testSubAccountId")
.setCustomization(customization);

...

chapa.initialize(postData);
```
Or, you can use a string JSON data.
Or, as a string JSON data.
```java
String formData = " { " +
String postDataString = " { " +
"'amount': '100', " +
"'currency': 'ETB'," +
"'email': 'abebe@bikila.com'," +
Expand All @@ -91,35 +123,34 @@ String formData = " { " +
" 'customization[logo]':'https://mylogo.com/log.png'" +
" }" +
" }";

chapa.initialize(postDataString)
```
Initialize payment
Intitialize payment
```java
InitializeResponseData responseData = chapa.initialize(postData);
// Get the response message
System.out.println(responseData.getMessage());
// Get the checkout URL from the response JSON
System.out.println(responseData.getData().getCheckOutUrl());
// Get the raw response JSON
System.out.println(responseData.getRawJson());
InitializeResponseData responseData = chapa.initialize(postData)
```
Verify payment
```java
// Get the verification response data
VerifyResponseData verifyResponseData = chapa.verify("tx-myecommerce12345");
VerifyResponseData actualResponseData = chapa.verify("tx-ref");
```
Get the list of banks
Get list of banks
```java
List<Bank> banks = chapa.getBanks();
```
To create a subaccount, you can specify your information by either using our `Subaccount` class.
```java
SubAccount subAccount = new SubAccount()
.setBusinessName("Abebe Suq")
.setAccountName("Abebe Bikila")
.setAccountNumber("0123456789")
.setBankCode("96e41186-29ba-4e30-b013-2ca36d7e7025")
.setSplitType(SplitType.PERCENTAGE)
.setSplitValue(0.2);
SubAccount subAccount = new SubAccountDto()
.setBusinessName("Abebe Suq")
.setAccountName("Abebe Bikila")
.setAccountNumber("0123456789")
.setBankCode("001")
.setSplitType(SplitTypeEnum.PERCENTAGE)
.setSplitValue(0.2);

...

SubAccountResponseData response = chapa.createSubAccount(subAccountDto);
```
Or, you can use a string JSON data.
```java
Expand All @@ -131,68 +162,81 @@ String subAccount = " { " +
"'split_type': 'percentage'," +
"'split_value': '0.2'" +
" }";

...

SubAccountResponseData actualResponse = chapa.createSubAccount(subAccount);
```
Create subaccount
```java
SubAccountResponseData subAccountResponseData = chapa.createSubAccount(subAccount);
// Get SubAccount id from the response JSOn
System.out.println(subAccountResponseData.getData().getSubAccountId());
SubAccountResponseData actualResponse = chapa.createSubAccount(subAccountDto);
```
## Example
```java
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
package com.github.amenski.chapa;

import com.github.amenski.Chapa;
import com.github.amenski.client.ChapaClient;
import com.github.amenski.ChapaException;
import com.github.amenski.model.Customization;
import com.github.amenski.model.PostData;
import com.github.amenski.model.ResponseBanks;
import com.github.amenski.model.SplitTypeEnum;
import com.github.amenski.model.SubAccountDto;

import io.github.yaphet17.chapa.Chapa;
import io.github.yaphet17.chapa.PostData;
import io.github.yaphet17.chapa.SubAccount;
import io.github.yaphet17.chapa.SplitType;
import io.github.yaphet17.chapa.Bank;
import java.math.BigDecimal;
import java.util.UUID;

public class ChapaExample {

public static void main(String[] args) {
Chapa chapa = new Chapa("your-secrete-key");

Customization customization = new Customization()
public static void main(String[] args) throws ChapaException {
Chapa chapa = new Chapa.ChapaBuilder()
.client(new ChapaClient())
.secretKey("CHASECK_TEST-...")
.build();

Customization customization = new Customization()
.setTitle("E-commerce")
.setDescription("It is time to pay")
.setLogo("https://mylogo.com/log.png");

PostData postData = new PostData()
PostData postData = new PostData()
.setAmount(new BigDecimal("100"))
.setCurrency("ETB")
.setFirstName("Abebe")
.setLastName("Bikila")
.setEmail("abebe@bikila")
.setTxRef(Util.generateToken())
.setEmail("abebe@bikila.com")
.setTxRef(UUID.randomUUID().toString())
.setCallbackUrl("https://chapa.co")
.setReturnUrl("https://chapa.co")
.setSubAccountId("testSubAccountId")
.setCustomization(customization);
SubAccount subAccount = new SubAccount()

SubAccountDto subAccountDto = new SubAccountDto()
.setBusinessName("Abebe Suq")
.setAccountName("Abebe Bikila")
.setAccountNumber("0123456789")
.setBankCode("96e41186-29ba-4e30-b013-2ca36d7e7025")
.setSplitType(SplitType.PERCENTAGE)
.setBankCode("853d0598-9c01-41ab-ac99-48eab4da1513")
.setSplitType(SplitTypeEnum.PERCENTAGE)
.setSplitValue(0.2);


InitializeResponseData responseData = chapa.initialize(postData);
VerifyResponseData verifyResponseData = chapa.verify("tx-myecommerce12345");
SubAccountResponseData subAccountResponseData = chapa.createSubAccount(subAccount);

}
}
// list of banks
ResponseBanks banks = chapa.getBanks();
if ((banks == null || banks.getData() == null)) {
System.out.println("Create SubAccount response: " + banks);
} else {
banks.getData().forEach(System.out::println);
}
// create subaccount
System.out.println("Create SubAccount response: " + chapa.createSubAccount(subAccountDto));
// initialize payment
System.out.println("Initialize response: " + chapa.initialize(postData));
// verify payment
System.out.println("Verify response: " + chapa.verify(postData.getTxRef()));
}
}
```
## Contribution
If you find any bugs or have any suggestions, please feel free to open an issue or pull request.
Please feel free to open an issue or pull request.

## License
This open-source library is licensed under the terms of the MIT License.

Enjoy!
MIT
41 changes: 41 additions & 0 deletions doc/allclasses-frame.html
@@ -0,0 +1,41 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<!-- NewPage -->
<html lang="en">
<head>
<!-- Generated by javadoc (1.8.0_392) on Mon Feb 05 08:31:18 CET 2024 -->
<title>All Classes</title>
<meta name="date" content="2024-02-05">
<link rel="stylesheet" type="text/css" href="stylesheet.css" title="Style">
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<h1 class="bar">All&nbsp;Classes</h1>
<div class="indexContainer">
<ul>
<li><a href="com/github/amenski/model/Bank.html" title="class in com.github.amenski.model" target="classFrame">Bank</a></li>
<li><a href="com/github/amenski/Chapa.html" title="class in com.github.amenski" target="classFrame">Chapa</a></li>
<li><a href="com/github/amenski/Chapa.ChapaBuilder.html" title="class in com.github.amenski" target="classFrame">Chapa.ChapaBuilder</a></li>
<li><a href="com/github/amenski/client/ChapaClient.html" title="class in com.github.amenski.client" target="classFrame">ChapaClient</a></li>
<li><a href="com/github/amenski/client/ChapaClientApi.html" title="interface in com.github.amenski.client" target="classFrame"><span class="interfaceName">ChapaClientApi</span></a></li>
<li><a href="com/github/amenski/exception/ChapaException.html" title="class in com.github.amenski.exception" target="classFrame">ChapaException</a></li>
<li><a href="com/github/amenski/model/Customization.html" title="class in com.github.amenski.model" target="classFrame">Customization</a></li>
<li><a href="com/github/amenski/client/provider/DefaultRetrofitClientProvider.html" title="class in com.github.amenski.client.provider" target="classFrame">DefaultRetrofitClientProvider</a></li>
<li><a href="com/github/amenski/client/IChapaClient.html" title="interface in com.github.amenski.client" target="classFrame"><span class="interfaceName">IChapaClient</span></a></li>
<li><a href="com/github/amenski/model/InitializeResponseData.html" title="class in com.github.amenski.model" target="classFrame">InitializeResponseData</a></li>
<li><a href="com/github/amenski/model/InitializeResponseData.Data.html" title="class in com.github.amenski.model" target="classFrame">InitializeResponseData.Data</a></li>
<li><a href="com/github/amenski/utility/LocalDateTimeDeserializer.html" title="class in com.github.amenski.utility" target="classFrame">LocalDateTimeDeserializer</a></li>
<li><a href="com/github/amenski/model/PostData.html" title="class in com.github.amenski.model" target="classFrame">PostData</a></li>
<li><a href="com/github/amenski/model/ResponseBanks.html" title="class in com.github.amenski.model" target="classFrame">ResponseBanks</a></li>
<li><a href="com/github/amenski/model/ResponseData.html" title="class in com.github.amenski.model" target="classFrame">ResponseData</a></li>
<li><a href="com/github/amenski/client/provider/RetrierRetrofitClientProvider.html" title="class in com.github.amenski.client.provider" target="classFrame">RetrierRetrofitClientProvider</a></li>
<li><a href="com/github/amenski/client/provider/RetrofitClientProvider.html" title="interface in com.github.amenski.client.provider" target="classFrame"><span class="interfaceName">RetrofitClientProvider</span></a></li>
<li><a href="com/github/amenski/model/SplitTypeEnum.html" title="enum in com.github.amenski.model" target="classFrame">SplitTypeEnum</a></li>
<li><a href="com/github/amenski/utility/StringUtils.html" title="class in com.github.amenski.utility" target="classFrame">StringUtils</a></li>
<li><a href="com/github/amenski/model/SubAccountDto.html" title="class in com.github.amenski.model" target="classFrame">SubAccountDto</a></li>
<li><a href="com/github/amenski/model/SubAccountResponseData.html" title="class in com.github.amenski.model" target="classFrame">SubAccountResponseData</a></li>
<li><a href="com/github/amenski/utility/Util.html" title="class in com.github.amenski.utility" target="classFrame">Util</a></li>
<li><a href="com/github/amenski/model/VerifyResponseData.html" title="class in com.github.amenski.model" target="classFrame">VerifyResponseData</a></li>
</ul>
</div>
</body>
</html>