├── gradle.properties ├── jitpack.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── libs ├── common │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── com │ │ │ └── africastalking │ │ │ ├── Logger.java │ │ │ ├── ATCash.java │ │ │ ├── Const.java │ │ │ ├── Status.java │ │ │ ├── Callback.java │ │ │ ├── Service.java │ │ │ └── AfricasTalking.java │ └── build.gradle ├── voice │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── voice │ │ ├── action │ │ │ ├── Reject.java │ │ │ ├── Conference.java │ │ │ ├── Play.java │ │ │ ├── Redirect.java │ │ │ ├── Dequeue.java │ │ │ ├── Enqueue.java │ │ │ ├── Say.java │ │ │ ├── Dial.java │ │ │ ├── Action.java │ │ │ ├── GetDigits.java │ │ │ └── Record.java │ │ ├── CallTransferResponse.java │ │ ├── CallResponse.java │ │ ├── QueuedCallsResponse.java │ │ └── CallEntry.java │ │ ├── IVoice.java │ │ ├── ActionBuilder.java │ │ └── VoiceService.java ├── airtime │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── IAirtime.java │ │ ├── airtime │ │ └── AirtimeResponse.java │ │ └── AirtimeService.java ├── application │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── IApplication.java │ │ ├── application │ │ └── ApplicationResponse.java │ │ └── ApplicationService.java ├── chat │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── chat │ │ ├── TextMessageBody.java │ │ ├── MessageBody.java │ │ ├── ChatResponse.java │ │ ├── ChatMessage.java │ │ ├── TemplateMessageBody.java │ │ ├── MediaMessageBody.java │ │ └── InteractiveMessageBody.java │ │ ├── IChat.java │ │ └── ChatService.java ├── sms │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── sms │ │ ├── Subscription.java │ │ ├── SubscriptionResponse.java │ │ ├── Recipient.java │ │ ├── Message.java │ │ ├── FetchSubscriptionResponse.java │ │ ├── SendMessageResponse.java │ │ └── FetchMessageResponse.java │ │ ├── IBulkSMS.java │ │ ├── IPremiumSMS.java │ │ └── SmsService.java ├── ussd │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ └── UssdService.java ├── insights │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── IInsights.java │ │ ├── insights │ │ ├── SimSwapResponse.java │ │ └── SwapResponse.java │ │ └── InsightsService.java ├── mobileData │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ ├── mobileData │ │ ├── response │ │ │ ├── WalletBalanceResponse.java │ │ │ ├── FindTransactionResponse.java │ │ │ └── MobileDataResponse.java │ │ ├── WalletTransaction.java │ │ ├── recipient │ │ │ └── MobileDataRecipient.java │ │ └── Transaction.java │ │ ├── IMobileData.java │ │ └── MobileDataService.java └── core │ ├── src │ ├── test │ │ ├── java │ │ │ └── com │ │ │ │ └── africastalking │ │ │ │ └── test │ │ │ │ ├── AfricasTalkingTest.java │ │ │ │ ├── Fixtures.java │ │ │ │ ├── insights │ │ │ │ └── InsightsTest.java │ │ │ │ ├── chat │ │ │ │ └── ChatTest.java │ │ │ │ ├── application │ │ │ │ └── ApplicationTest.java │ │ │ │ ├── airtime │ │ │ │ └── AirtimeTest.java │ │ │ │ ├── mobileData │ │ │ │ └── MobileDataTest.java │ │ │ │ ├── voice │ │ │ │ └── VoiceTest.java │ │ │ │ └── sms │ │ │ │ └── SmsTest.java │ │ └── resources │ │ │ └── cert │ │ │ ├── req.csr │ │ │ ├── cert.pem │ │ │ └── key.pem │ └── main │ │ └── java │ │ └── com │ │ └── africastalking │ │ └── AT.java │ └── build.gradle ├── example ├── README.md ├── build.gradle └── src │ └── main │ ├── resources │ ├── public │ │ ├── css │ │ │ └── style.css │ │ └── js │ │ │ └── main.js │ └── views │ │ └── index.hbs │ └── java │ └── com │ └── africastalking │ └── example │ └── App.java ├── settings.gradle ├── CHANGELOG ├── LICENSE ├── gradlew.bat ├── .gitignore ├── gradlew └── README.md /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M -------------------------------------------------------------------------------- /jitpack.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - ./gradlew clean build publishToMavenLocal -xtest -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AfricasTalkingLtd/africastalking-java/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/Logger.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | public interface Logger { 4 | void log(String message, Object... args); 5 | } 6 | -------------------------------------------------------------------------------- /libs/voice/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | api project(":common") 5 | testImplementation group: 'junit', name: 'junit', version: '4.11' 6 | } 7 | -------------------------------------------------------------------------------- /libs/airtime/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | 7 | testImplementation group: 'junit', name: 'junit', version: '4.11' 8 | } 9 | -------------------------------------------------------------------------------- /libs/application/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | api project(":common") 5 | 6 | testImplementation group: 'junit', name: 'junit', version: '4.11' 7 | } 8 | -------------------------------------------------------------------------------- /libs/chat/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | 7 | testImplementation group: 'junit', name: 'junit', version: '4.11' 8 | } 9 | -------------------------------------------------------------------------------- /libs/sms/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | 7 | testImplementation group: 'junit', name: 'junit', version: '4.11' 8 | } 9 | -------------------------------------------------------------------------------- /libs/ussd/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | 7 | testImplementation group: 'junit', name: 'junit', version: '4.11' 8 | } 9 | -------------------------------------------------------------------------------- /libs/insights/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | api 'com.google.code.gson:gson:2.8.0' 7 | 8 | 9 | testImplementation group: 'junit', name: 'junit', version: '4.11' 10 | } 11 | -------------------------------------------------------------------------------- /libs/mobileData/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | 5 | api project(":common") 6 | api 'com.google.code.gson:gson:2.8.0' 7 | 8 | 9 | testImplementation group: 'junit', name: 'junit', version: '4.11' 10 | } 11 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Reject.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | public class Reject extends Action { 4 | 5 | /** 6 | * Reject 7 | */ 8 | public Reject() { 9 | this.tag = "Reject"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Feb 02 09:05:24 EAT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 7 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/TextMessageBody.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | final public class TextMessageBody extends MessageBody { 4 | public String message; 5 | public TextMessageBody(String text) { 6 | this.message = text; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/MessageBody.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public abstract class MessageBody { 6 | @Override 7 | public String toString() { 8 | return new Gson().toJson(this); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Conference.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | public class Conference extends Action { 4 | /** 5 | * Conference 6 | */ 7 | public Conference() { 8 | this.tag = "Conference"; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/ATCash.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public class ATCash { 6 | public double amount; 7 | public String currencyCode; 8 | @Override 9 | public String toString() { 10 | return new Gson().toJson(this); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/Const.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | class Const { 4 | public static final String PRODUCTION_DOMAIN = "africastalking.com"; 5 | public static final String SANDBOX_DOMAIN = "sandbox.africastalking.com"; 6 | public static final String INTL_PHONE_FORMAT = "^\\+\\d{1,3}\\d{3,}$"; 7 | } 8 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Play.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | 5 | public class Play extends Action { 6 | /** 7 | * Play 8 | * @param url 9 | */ 10 | public Play(URL url) { 11 | this.tag = "Play"; 12 | this.attributes.put("url", url.toString()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/Subscription.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public final class Subscription { 6 | public long id; 7 | public String phoneNumber; 8 | public String date; 9 | 10 | @Override 11 | public String toString() { 12 | return new Gson().toJson(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/SubscriptionResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | public final class SubscriptionResponse { 7 | public String status; 8 | public String description; 9 | 10 | @Override 11 | public String toString() { 12 | return new Gson().toJson(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/CallTransferResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | public final class CallTransferResponse { 7 | public String status; 8 | public String errorMessage; 9 | 10 | @Override 11 | public String toString() { 12 | return new Gson().toJson(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Redirect.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | 5 | public class Redirect extends Action { 6 | 7 | /** 8 | * Redirect 9 | * @param url 10 | */ 11 | public Redirect(URL url) { 12 | this.tag = "Redirect"; 13 | this.text = url.toString(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/ChatResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | import com.google.gson.Gson; 4 | 5 | final public class ChatResponse { 6 | public String messageId; 7 | public String status; 8 | public String phoneNumber; 9 | 10 | @Override 11 | public String toString() { 12 | return new Gson().toJson(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/CallResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice; 2 | 3 | 4 | import com.google.gson.Gson; 5 | import java.util.List; 6 | 7 | public final class CallResponse { 8 | public List entries; 9 | public String errorMessage; 10 | 11 | @Override 12 | public String toString() { 13 | return new Gson().toJson(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/response/WalletBalanceResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData.response; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public final class WalletBalanceResponse { 6 | public String status; 7 | public String errorMessage; 8 | public String balance; 9 | 10 | @Override 11 | public String toString() { 12 | return new Gson().toJson(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/Recipient.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | public final class Recipient { 7 | public int statusCode; 8 | public String number; 9 | public String cost; 10 | public String status; 11 | public String messageId; 12 | 13 | @Override 14 | public String toString() { 15 | return new Gson().toJson(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libs/application/src/main/java/com/africastalking/IApplication.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.application.ApplicationResponse; 4 | 5 | import retrofit2.Call; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Query; 8 | 9 | /** 10 | * Account Endpoints 11 | */ 12 | interface IApplication { 13 | @GET("user") 14 | Call fetchApplicationData(@Query("username") String username); 15 | } 16 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | **Run** 4 | 5 | Make sure your `local.properties` file has the following content then run `./gradlew runApp` 6 | 7 | ```ini 8 | # AT API 9 | api.username=sandbox 10 | api.key=YOUR_SANDBOX_API_KEY 11 | 12 | # Bintray 13 | bintray.user=fake 14 | bintray.key=fake 15 | bintray.repo=fake 16 | bintray.organization=fake 17 | bintray.package=fake 18 | bintray.groupId=fake 19 | bintray.version=fake 20 | bintray.vscUrl=fake 21 | ``` -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/Message.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | public final class Message { 7 | public String from; 8 | public String to; 9 | public String text; 10 | public String linkId; 11 | public String date; 12 | public long id; 13 | 14 | @Override 15 | public String toString() { 16 | return new Gson().toJson(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/airtime/src/main/java/com/africastalking/IAirtime.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.airtime.AirtimeResponse; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.*; 8 | 9 | interface IAirtime { 10 | 11 | @FormUrlEncoded 12 | @POST("send") 13 | Call send(@Field("username") String username, @Field("recipients") String recipients, @Field("maxNumRetry") String maxNumRetry); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/QueuedCallsResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | import java.util.List; 7 | 8 | public final class QueuedCallsResponse { 9 | public String status; 10 | public String errorMessage; 11 | public List entries; 12 | 13 | @Override 14 | public String toString() { 15 | return new Gson().toJson(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libs/insights/src/main/java/com/africastalking/IInsights.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.insights.SimSwapResponse; 4 | import retrofit2.Call; 5 | import retrofit2.http.Body; 6 | import retrofit2.http.Headers; 7 | import retrofit2.http.POST; 8 | 9 | interface IInsights { 10 | 11 | @Headers("Content-Type: application/json") 12 | @POST("v1/sim-swap") 13 | Call checkSimSwapState(@Body String body); 14 | } 15 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/CallEntry.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public final class CallEntry { 6 | public String status; 7 | public String phoneNumber; 8 | public String sessionId; 9 | public String queueName = null; 10 | public int numCalls = 1; 11 | 12 | @Override 13 | public String toString() { 14 | return new Gson().toJson(this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | final public class ChatMessage { 9 | public String username; 10 | public String waNumber; 11 | public String phoneNumber; 12 | 13 | public MessageBody body; 14 | 15 | @Override 16 | public String toString() { 17 | return new Gson().toJson(this); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Africa's Talking SDK 3 | */ 4 | 5 | /* Libs */ 6 | Set libs = new HashSet() 7 | libs << 'sms' 8 | libs << 'chat' 9 | libs << 'mobileData' 10 | libs << 'token' 11 | libs << 'airtime' 12 | libs << 'voice' 13 | libs << 'insights' 14 | libs << 'ussd' 15 | libs << 'application' 16 | libs << 'common' 17 | libs << 'core' 18 | 19 | libs.each { lib -> 20 | include lib 21 | project(":${lib}").projectDir = file("libs/${lib}") 22 | } 23 | 24 | /* Example */ 25 | include 'example' 26 | -------------------------------------------------------------------------------- /libs/insights/src/main/java/com/africastalking/insights/SimSwapResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.insights; 2 | 3 | import com.africastalking.ATCash; 4 | import com.google.gson.Gson; 5 | 6 | import java.util.List; 7 | 8 | public class SimSwapResponse { 9 | 10 | public String transactionId; 11 | public String status; 12 | public ATCash totalCost; 13 | public List responses; 14 | 15 | @Override 16 | public String toString() { 17 | return new Gson().toJson(this); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/WalletTransaction.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData; 2 | 3 | import com.google.gson.Gson; 4 | 5 | public final class WalletTransaction { 6 | public String value; 7 | public String balance; 8 | public String category; 9 | public String description; 10 | public String transactionId; 11 | public Transaction transactionData; 12 | 13 | @Override 14 | public String toString() { 15 | return new Gson().toJson(this); 16 | } 17 | } -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/response/FindTransactionResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData.response; 2 | 3 | import com.google.gson.Gson; 4 | import com.africastalking.mobileData.Transaction; 5 | 6 | import java.util.List; 7 | 8 | public final class FindTransactionResponse { 9 | public Transaction data; 10 | public String status; 11 | public String errorMessage; 12 | 13 | @Override 14 | public String toString() { 15 | return new Gson().toJson(this); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/FetchSubscriptionResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | 4 | import com.google.gson.Gson; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public final class FetchSubscriptionResponse { 11 | @SerializedName("responses") 12 | public List subscriptions = new ArrayList<>(); 13 | 14 | @Override 15 | public String toString() { 16 | return new Gson().toJson(this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/IChat.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.chat.*; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.*; 8 | 9 | interface IChat { 10 | 11 | @Headers("Content-Type: application/json") 12 | @POST("whatsapp/message/send") 13 | Call sendMessage(@Body ChatMessage message); 14 | 15 | 16 | @Headers("Content-Type: application/json") 17 | @POST("whatsapp/template/send") 18 | Call sendTemplate(@Body ChatMessage message); 19 | } 20 | -------------------------------------------------------------------------------- /libs/common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | targetCompatibility = 1.8 4 | sourceCompatibility = 1.8 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | 12 | api 'com.squareup.retrofit2:retrofit:2.1.0' 13 | api 'com.squareup.retrofit2:converter-scalars:2.1.0' 14 | api 'com.squareup.retrofit2:converter-gson:2.1.0' 15 | api 'com.squareup.okhttp3:logging-interceptor:3.5.0' 16 | api 'org.valid4j:valid4j:0.5.0' 17 | 18 | 19 | testImplementation group: 'junit', name: 'junit', version: '4.11' 20 | } 21 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/TemplateMessageBody.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public final class TemplateMessageBody extends MessageBody { 7 | public String templateId; 8 | public String headerValue = null; 9 | public List bodyValues = new ArrayList(); 10 | 11 | public TemplateMessageBody(String templateId, String header, List body) { 12 | this.templateId = templateId; 13 | this.headerValue = header; 14 | this.bodyValues = body; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Dequeue.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | public class Dequeue extends Action { 4 | 5 | /** 6 | * Dequeue 7 | * @param name 8 | * @param phoneNumber 9 | */ 10 | public Dequeue(String name, String phoneNumber) { 11 | this.tag = "Dequeue"; 12 | this.attributes.put("phoneNumber", phoneNumber); 13 | if (name != null) { 14 | this.attributes.put("name", name); 15 | } 16 | } 17 | 18 | public Dequeue(String phoneNumber) { 19 | this(null, phoneNumber); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/Status.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | public final class Status { 4 | 5 | public static final String PENDING_CONFIRMATION = "PendingConfirmation"; 6 | public static final String PENDING_VALIDATION = "PendingValidation"; 7 | public static final String INVALID_REQUEST = "InvalidRequest"; 8 | public static final String NOT_SUPPORTED = "NotSupported"; 9 | public static final String SUCCESS = "Success"; 10 | public static final String FAILED = "Failed"; 11 | public static final String QUEUED = "Queued"; 12 | public static final String SENT = "Sent"; 13 | } 14 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/SendMessageResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.annotations.SerializedName; 5 | import java.util.List; 6 | 7 | public final class SendMessageResponse { 8 | 9 | @SerializedName("SMSMessageData") 10 | public SmsMessageData data; 11 | 12 | public static final class SmsMessageData { 13 | @SerializedName("Recipients") 14 | public List recipients; 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return new Gson().toJson(this); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/AfricasTalkingTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test; 2 | 3 | import com.africastalking.*; 4 | import org.junit.Test; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | 8 | public class AfricasTalkingTest { 9 | 10 | 11 | @Test 12 | public void testInitialization() { 13 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 14 | ApplicationService ac1 = AfricasTalking.getService(ApplicationService.class); 15 | ApplicationService ac2 = AfricasTalking.getService(AfricasTalking.SERVICE_APPLICATION); 16 | assertEquals(ac1, ac2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/sms/FetchMessageResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.sms; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public final class FetchMessageResponse { 10 | 11 | @SerializedName("SMSMessageData") 12 | public SmsMessageData data; 13 | 14 | public static final class SmsMessageData { 15 | @SerializedName("Messages") 16 | public List messages = new ArrayList<>(); 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return new Gson().toJson(this); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Enqueue.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | 5 | public class Enqueue extends Action { 6 | 7 | /** 8 | * Enqueue 9 | * @param name 10 | * @param holdMusic 11 | */ 12 | public Enqueue(String name, URL holdMusic) { 13 | this.tag = "Enqueue"; 14 | if (holdMusic != null) { 15 | this.attributes.put("holdMusic", holdMusic.toString()); 16 | } 17 | if (name != null) { 18 | this.attributes.put("name", name); 19 | } 20 | } 21 | 22 | public Enqueue() { 23 | this(null, null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/application/src/main/java/com/africastalking/application/ApplicationResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.application; 2 | 3 | 4 | import com.google.gson.Gson; 5 | import com.google.gson.annotations.SerializedName; 6 | 7 | public final class ApplicationResponse { 8 | 9 | @SerializedName("UserData") 10 | public UserData userData; 11 | 12 | public static final class UserData { 13 | public String balance; 14 | 15 | @Override 16 | public String toString() { 17 | return new Gson().toJson(this); 18 | } 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return new Gson().toJson(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/response/MobileDataResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData.response; 2 | 3 | import com.google.gson.Gson; 4 | 5 | import java.util.List; 6 | 7 | public final class MobileDataResponse { 8 | 9 | public String errorMessage = null; 10 | public List entries; 11 | 12 | public static class MobileDataEntry { 13 | public String phoneNumber; 14 | public String status; 15 | public String provider; 16 | public String value; 17 | public String transactionId; 18 | public String errorMessage = null; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return new Gson().toJson(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/core/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | apply plugin: 'java-library' 3 | apply plugin: 'application' 4 | mainClassName = "com.africastalking.AT" 5 | 6 | targetCompatibility = 1.8 7 | sourceCompatibility = 1.8 8 | 9 | test { 10 | testLogging { 11 | // Make sure output from 12 | // standard out or error is shown 13 | // in Gradle output. 14 | showStandardStreams = true 15 | } 16 | } 17 | 18 | dependencies { 19 | api project(":airtime") 20 | api project(":mobileData") 21 | api project(":sms") 22 | api project(":chat") 23 | api project(":ussd") 24 | api project(":voice") 25 | api project(":insights") 26 | api project(":application") 27 | 28 | testImplementation group: 'junit', name: 'junit', version: '4.11' 29 | } 30 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/MediaMessageBody.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | final public class MediaMessageBody extends MessageBody { 4 | public String mediaType; 5 | public String url; 6 | public String caption; 7 | 8 | public enum MediaType { 9 | Image, 10 | Audio, 11 | Video, 12 | Voice, 13 | Document 14 | } 15 | 16 | 17 | public MediaMessageBody(MediaType mediaType, String url) { 18 | this.mediaType = mediaType.name(); 19 | this.url = url; 20 | this.caption = null; 21 | } 22 | 23 | public MediaMessageBody(MediaType mediaType, String url, String caption) { 24 | this(mediaType, url); 25 | this.caption = caption; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/Callback.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | /** 5 | * Communicates responses from a server. One and only one method will be 6 | * invoked in response to a given request. 7 | * @param expected result type 8 | */ 9 | public interface Callback { 10 | 11 | /** 12 | * Invoked for a received response. 13 | * @param data T Received data 14 | */ 15 | void onSuccess(T data); 16 | 17 | 18 | /** 19 | * Invoked when a network exception occurred talking to the server or when an unexpected 20 | * exception occurred creating the request or processing the response. 21 | * @param throwable Thrown error or exception 22 | */ 23 | void onFailure(Throwable throwable); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/IMobileData.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.mobileData.response.*; 5 | 6 | import retrofit2.Call; 7 | import retrofit2.http.GET; 8 | import retrofit2.http.Body; 9 | import retrofit2.http.POST; 10 | import retrofit2.http.QueryMap; 11 | 12 | import java.util.HashMap; 13 | 14 | interface IMobileData { 15 | @POST("mobile/data/request") 16 | Call requestMobileData(@Body HashMap body); 17 | 18 | @GET("/query/transaction/find") 19 | Call findTransaction(@QueryMap HashMap query); 20 | 21 | @GET("/query/wallet/balance") 22 | Call fetchWalletBalance(@QueryMap HashMap query); 23 | } 24 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/IBulkSMS.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.sms.FetchMessageResponse; 5 | import com.africastalking.sms.SendMessageResponse; 6 | import retrofit2.Call; 7 | import retrofit2.http.*; 8 | 9 | interface IBulkSMS { 10 | 11 | @FormUrlEncoded 12 | @POST("messaging") 13 | Call send(@Field("username") String username, @Field("to") String to, 14 | @Field("from") String from, @Field("message") String message, 15 | @Field("bulkSMSMode") int bulkMode, @Field("enqueue") String enqueue); 16 | 17 | @GET("messaging") 18 | Call fetchMessages(@Query("username") String username, @Query("lastReceivedId") long lastReceivedId); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /libs/core/src/main/java/com/africastalking/AT.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.application.ApplicationResponse; 4 | 5 | import java.io.IOException; 6 | 7 | public final class AT { 8 | 9 | private static void log(String message) { 10 | System.out.println(message); 11 | } 12 | 13 | public static void main(String[] argv) { 14 | log("\nAfrica's Talking SDK\n"); 15 | AfricasTalking.initialize(argv[0], argv[1]); 16 | try { 17 | log("\tGetting app account info...\n"); 18 | ApplicationResponse resp = AfricasTalking.getService(ApplicationService.class).fetchApplicationData(); 19 | log("\tBalance: " + resp.userData.balance); 20 | } catch (IOException e) { 21 | System.out.println("Failed to get info!"); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /libs/insights/src/main/java/com/africastalking/insights/SwapResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.insights; 2 | 3 | import com.africastalking.ATCash; 4 | import com.google.gson.Gson; 5 | 6 | 7 | public class SwapResponse { 8 | 9 | public String status; 10 | public String requestId; 11 | public ATCash cost; 12 | public PhoneNumber phoneNumber; 13 | 14 | static class PhoneNumber { 15 | public String carrierName; 16 | public int countryCode; 17 | public String networkCode; 18 | public String number; 19 | public String numberType; 20 | @Override 21 | public String toString() { 22 | return new Gson().toJson(this); 23 | } 24 | } 25 | 26 | 27 | @Override 28 | public String toString() { 29 | return new Gson().toJson(this); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | ## [3.5.1-3] 5 | 6 | ### Updated 7 | 8 | - Chat service clean up 9 | - Remove unnecessary code 10 | 11 | ## [3.5.0] 12 | 13 | ### Added 14 | 15 | - Insights service 16 | 17 | ## [3.4.7-3.4.11] 18 | 19 | ### Added 20 | 21 | - Temporarily move to jitpack & clean up 22 | 23 | ## [3.4.6] 24 | 25 | ### Added 26 | 27 | - Allow users to get service instances for different username/apiKey combinations. 28 | 29 | ## [3.4.0 - 3.4.5] 30 | 31 | - Bug fixes 32 | 33 | ## [3.3.9] 34 | 35 | ### Added 36 | 37 | - Payment wallet balance query 38 | - Payment transactions query 39 | 40 | ### Fixes 41 | 42 | - SMS high volume tests 43 | - Ugrade grpc 44 | 45 | 46 | ## [3.3.8] 47 | 48 | ### Added 49 | 50 | - CHANGELOG 51 | 52 | ### Fixed 53 | 54 | - SMS fetch messages 55 | -------------------------------------------------------------------------------- /example/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.africastalking.example' 2 | version '1.0.0' 3 | 4 | apply plugin: 'application' 5 | mainClassName = "com.africastalking.example.App" 6 | 7 | sourceCompatibility = 1.8 8 | targetCompatibility = 1.8 9 | 10 | repositories { 11 | jcenter() 12 | maven { url 'https://jitpack.io' } 13 | } 14 | 15 | dependencies { 16 | implementation 'org.slf4j:slf4j-simple:1.7.21' 17 | implementation 'com.sparkjava:spark-core:2.5.4' 18 | implementation 'com.sparkjava:spark-template-handlebars:2.3' 19 | implementation 'com.google.code.gson:gson:2.8.6' 20 | implementation 'com.github.AfricasTalkingLtd.africastalking-java:core:v3.5.3' 21 | testImplementation 'junit:junit:4.11' 22 | } 23 | 24 | task runApp(type: JavaExec, dependsOn: classes){ 25 | description = "Simple App Server" 26 | main = "com.africastalking.example.App" 27 | classpath = sourceSets.main.runtimeClasspath 28 | } -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/recipient/MobileDataRecipient.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData.recipient; 2 | 3 | import java.util.HashMap; 4 | 5 | public class MobileDataRecipient { 6 | 7 | public static enum DataUnit { MB, GB } 8 | 9 | public static enum DataValidity { Day, Week, Month } 10 | 11 | public String phoneNumber; 12 | public int quantity; 13 | public DataUnit unit; 14 | public DataValidity validity; 15 | public HashMap metadata = new HashMap<>(); 16 | 17 | public MobileDataRecipient(String phoneNumber, int quantity, DataUnit unit, DataValidity validity) { 18 | this.phoneNumber = phoneNumber; 19 | this.quantity = quantity; 20 | this.unit = unit; 21 | this.validity = validity; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return super.toString(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/Fixtures.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test; 2 | 3 | import java.io.FileInputStream; 4 | import java.io.IOException; 5 | import java.util.Properties; 6 | 7 | public final class Fixtures { 8 | public static String API_KEY; 9 | public static String USERNAME; 10 | public static final boolean DEBUG = true; 11 | public static final long TIMEOUT = 3500; 12 | 13 | static { 14 | try { 15 | String filePath = "../../local.properties"; // relative to libs/core 16 | Properties properties = new Properties(); 17 | FileInputStream is = new FileInputStream(filePath); 18 | properties.load(is); 19 | API_KEY = properties.getProperty("api.key"); 20 | USERNAME = properties.getProperty("api.username"); 21 | } catch (IOException e) { 22 | e.printStackTrace(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /libs/airtime/src/main/java/com/africastalking/airtime/AirtimeResponse.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.airtime; 2 | 3 | 4 | import com.google.gson.Gson; 5 | 6 | import java.util.List; 7 | 8 | public class AirtimeResponse { 9 | 10 | public int numSent; 11 | public String totalAmount; 12 | public String totalDiscount; 13 | public String errorMessage; 14 | public List responses; 15 | 16 | public static class AirtimeEntry { 17 | public String errorMessage; 18 | public String phoneNumber; 19 | public String amount; 20 | public String discount; 21 | public String status; 22 | public String requestId; 23 | 24 | @Override 25 | public String toString() { 26 | return new Gson().toJson(this); 27 | } 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return new Gson().toJson(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Say.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | public class Say extends Action { 4 | 5 | public enum Voice { MAN, WOMAN } 6 | 7 | /** 8 | * Say 9 | * @param text 10 | * @param playBeep 11 | * @param voice 12 | */ 13 | public Say(String text, boolean playBeep, Voice voice) { 14 | this.tag = "Say"; 15 | this.text = text; 16 | 17 | if (playBeep) { 18 | this.attributes.put("playBeep", "true"); 19 | } 20 | 21 | if (voice != null) { 22 | this.attributes.put("voice", voice.name().toLowerCase()); 23 | } 24 | } 25 | 26 | public Say(String text) { 27 | this(text, false, null); 28 | } 29 | 30 | public Say(String text, Voice voice) { 31 | this(text, false, voice); 32 | } 33 | 34 | public Say(String text, boolean playBeep) { 35 | this(text, playBeep, null); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/mobileData/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.mobileData; 2 | 3 | import java.util.HashMap; 4 | 5 | import com.google.gson.Gson; 6 | 7 | 8 | public final class Transaction { 9 | public String value; 10 | public String source; 11 | public String status; 12 | public String provider; 13 | public String category; 14 | public String sourceType; 15 | public String productName; 16 | public String destination; 17 | public String description; 18 | public String creationTime; 19 | public String providerRefId; 20 | public String transactionId; 21 | public String transactionFee; 22 | public String providerChannel; 23 | public String destinationType; 24 | public String transactionDate; 25 | public HashMap requestMetadata; 26 | public HashMap providerMetadata; 27 | 28 | @Override 29 | public String toString() { 30 | return new Gson().toJson(this); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Africa's Talking 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /libs/core/src/test/resources/cert/req.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC7jCCAdYCAQAwgagxCzAJBgNVBAYTAktFMRcwFQYDVQQIEw5OYWlyb2JpIENv 3 | dW50eTEQMA4GA1UEBxMHTmFpcm9iaTEdMBsGA1UEChMUQWZyaWNhJ3MgVGFsa2lu 4 | ZyBMdGQxDjAMBgNVBAsTBVVJL1VYMRIwEAYDVQQDEwlsb2NhbGhvc3QxKzApBgkq 5 | hkiG9w0BCQEWHHNiYWxla2FnZUBhZnJpY2FzdGFsa2luZy5jb20wggEiMA0GCSqG 6 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCfpwv6i+ymn+Ozk2Ii1hHXGD7jYPQ7SuJv 7 | FjjWjfbgExMTM33D2olJxyX+eCQIzhKMxJL97oBLlIjT/A/+oMi8hAW1lMqpEwR1 8 | wa9Ntrn1rrLPpMvckQV1/EWaF3eOEa6QJPXzUf/pbemSET9cw1Ue4WAqowhONQLz 9 | nunhjuyqvpc3ZknmQCze+KxsYAUmWatxGw2dBhPC/yuZLWLRaCeoX7FxydP3y02b 10 | cVRtaoNZK3EJUnnDcXsbgpdP4AB++NjXyW3IvivLHp9dU2wjVaegwALTq6RM6cyo 11 | 2TLN3fAtBFk4cikDrlTS7fGkFZCfN0HmJdoPJ25nDyCtT7OPNYtxAgMBAAGgADAN 12 | BgkqhkiG9w0BAQUFAAOCAQEAiknxUXr6fvQ8gn1Qa87K3KmXRbXFSY5o85cPBwCH 13 | Rysd6EbByOX/w2PfRTNVRrWsD6eCHgExpqhJpX9QRiFwcAgtwyuBoRwAAdmfJcuO 14 | G1H9I6UeecZ1thQPMXpQKJYlBOm2sQacyW9FBPUNlI6ewBnZwwIPWojotc9/WI+A 15 | wEnWa8Mg2pI3M8za/SFc10kVzTxM2q+EjWRthPzZBd+wt2r1awWwBJQ0VT0+Ld0l 16 | 1W+YSKQq3XRFm7d2UCMvypvWGv7L0/ra3qe5g0nyrzL8M1U3FAUbk0aKM1RzKSyb 17 | raTQP4I5VvuZAnIWYOtf5MzKz0VQJq2cWqTgZC7cPeR5qg== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/insights/InsightsTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.insights; 2 | 3 | import com.africastalking.AfricasTalking; 4 | import com.africastalking.InsightsService; 5 | import com.africastalking.insights.SimSwapResponse; 6 | import com.africastalking.test.Fixtures; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | 14 | public class InsightsTest { 15 | 16 | 17 | @Before 18 | public void setup() { 19 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 20 | } 21 | 22 | @Test 23 | public void testCheckSimSwapState() { 24 | InsightsService service = AfricasTalking.getService(InsightsService.class); 25 | try { 26 | ArrayList phones = new ArrayList<>(); 27 | phones.add("+254710000000"); 28 | phones.add("+254770000000"); 29 | final SimSwapResponse response = service.checkSimSwapState(phones); 30 | Assert.assertEquals("Processed", response.status); 31 | } catch (IOException e) { 32 | Assert.fail(e.getMessage()); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /libs/ussd/src/main/java/com/africastalking/UssdService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | public final class UssdService extends Service { 5 | 6 | public static final String SESSION_CONTINUE = "CON"; 7 | public static final String SESSION_END = "END"; 8 | 9 | public static final String FLAG_SESSION_ID = "sessionId"; 10 | public static final String FLAG_SERVICE_CODE = "serviceCode"; 11 | public static final String FLAG_PHONE_NUMBER = "phoneNumber"; 12 | public static final String FLAG_TEXT = "text"; 13 | 14 | 15 | private static UssdService sInstance; 16 | 17 | private UssdService(String username, String apiKey) { 18 | super(username, apiKey); 19 | } 20 | 21 | UssdService() { 22 | super(); 23 | } 24 | 25 | @Override 26 | protected UssdService getInstance(String username, String apiKey) { 27 | 28 | if (sInstance == null) { 29 | sInstance = new UssdService(username, apiKey); 30 | } 31 | 32 | return sInstance; 33 | } 34 | 35 | @Override 36 | protected void initService() { } 37 | 38 | @Override 39 | protected boolean isInitialized() { 40 | return sInstance != null; 41 | } 42 | 43 | @Override 44 | protected void destroyService() { 45 | if (sInstance != null) { 46 | sInstance = null; 47 | } 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Dial.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | import java.util.List; 5 | 6 | public class Dial extends Action { 7 | /** 8 | * Dial 9 | * @param phoneNumbers 10 | * @param record 11 | * @param sequential 12 | * @param callerId 13 | * @param ringBackTone 14 | * @param maxDuration 15 | */ 16 | public Dial(List phoneNumbers, boolean record, boolean sequential, String callerId, URL ringBackTone, int maxDuration) { 17 | this.tag = "Dial"; 18 | 19 | this.attributes.put("phoneNumbers", String.join(",", phoneNumbers)); 20 | 21 | if (record) { 22 | this.attributes.put("record", "true"); 23 | } 24 | if (sequential) { 25 | this.attributes.put("sequential", "true"); 26 | } 27 | if (callerId != null) { 28 | this.attributes.put("callerId", callerId); 29 | } 30 | if (ringBackTone != null) { 31 | this.attributes.put("ringBackTone", ringBackTone.toString()); 32 | } 33 | if (maxDuration > 0) { 34 | this.attributes.put("maxDuration", String.valueOf(maxDuration)); 35 | } 36 | } 37 | 38 | public Dial(List phoneNumbers) { 39 | this(phoneNumbers, false, false, null, null, -1); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Action.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.List; 7 | 8 | public class Action { 9 | 10 | protected String tag; 11 | protected String text; 12 | protected HashMap attributes = new HashMap<>(); 13 | protected List children = new ArrayList<>(); 14 | 15 | public String build() { 16 | StringBuilder str = new StringBuilder(); 17 | str.append("<" + tag); 18 | 19 | if (!attributes.isEmpty()) { 20 | Iterator it = attributes.keySet().iterator(); 21 | while (it.hasNext()) { 22 | String key = it.next(); 23 | str.append(" " + key + "=\"" + attributes.get(key) + "\""); 24 | } 25 | } 26 | 27 | // Can't have text and children ?? 28 | if (!children.isEmpty()) { 29 | str.append(">"); 30 | for (Action child : children) { 31 | str.append(child.build()); 32 | } 33 | str.append(""); 34 | } else { // text 35 | if (text != null) { 36 | str.append(">" + text + ""); 37 | } else { 38 | str.append("/>"); 39 | } 40 | } 41 | 42 | return str.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/core/src/test/resources/cert/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDzjCCArYCCQDna/PCf//voTANBgkqhkiG9w0BAQsFADCBqDELMAkGA1UEBhMC 3 | S0UxFzAVBgNVBAgTDk5haXJvYmkgQ291bnR5MRAwDgYDVQQHEwdOYWlyb2JpMR0w 4 | GwYDVQQKExRBZnJpY2EncyBUYWxraW5nIEx0ZDEOMAwGA1UECxMFVUkvVVgxEjAQ 5 | BgNVBAMTCWxvY2FsaG9zdDErMCkGCSqGSIb3DQEJARYcc2JhbGVrYWdlQGFmcmlj 6 | YXN0YWxraW5nLmNvbTAeFw0xNzA3MTAxMDI4MDhaFw0xODA3MTAxMDI4MDhaMIGo 7 | MQswCQYDVQQGEwJLRTEXMBUGA1UECBMOTmFpcm9iaSBDb3VudHkxEDAOBgNVBAcT 8 | B05haXJvYmkxHTAbBgNVBAoTFEFmcmljYSdzIFRhbGtpbmcgTHRkMQ4wDAYDVQQL 9 | EwVVSS9VWDESMBAGA1UEAxMJbG9jYWxob3N0MSswKQYJKoZIhvcNAQkBFhxzYmFs 10 | ZWthZ2VAYWZyaWNhc3RhbGtpbmcuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 11 | MIIBCgKCAQEAn6cL+ovspp/js5NiItYR1xg+42D0O0ribxY41o324BMTEzN9w9qJ 12 | Sccl/ngkCM4SjMSS/e6AS5SI0/wP/qDIvIQFtZTKqRMEdcGvTba59a6yz6TL3JEF 13 | dfxFmhd3jhGukCT181H/6W3pkhE/XMNVHuFgKqMITjUC857p4Y7sqr6XN2ZJ5kAs 14 | 3visbGAFJlmrcRsNnQYTwv8rmS1i0WgnqF+xccnT98tNm3FUbWqDWStxCVJ5w3F7 15 | G4KXT+AAfvjY18ltyL4ryx6fXVNsI1WnoMAC06ukTOnMqNkyzd3wLQRZOHIpA65U 16 | 0u3xpBWQnzdB5iXaDyduZw8grU+zjzWLcQIDAQABMA0GCSqGSIb3DQEBCwUAA4IB 17 | AQA+TVAqGTiLQd79/QUE94W6jT8qMvdbx9QHV6lRHrKs9V7wvdUdVItElpXFjPRN 18 | ec0rH0OouTYRkVMy4nnxyzk4l8N2cbTFjcnfXpOWBanHNXwO/qhen63INrC8TeYF 19 | mHaM7DdPQ1stEoXdlFBor8vjwYjEZCt6SIYvqDrcYzef1wB8eizsBvNXOGG+vGPt 20 | vi7mQQpAEaa7AOaXWvDWHS0XjsMRaVczrAwTwFLHJsZsXR/dSNZffnQQ0J85f3XX 21 | GI5iOydMnz7tKrIqXubpXN5T7GBwX2RMm4A875VuYeUIZERTqhC0lDagN11W5DxU 22 | Zu0FCELgwazOEjI7U8FMFoZ5 23 | -----END CERTIFICATE----- 24 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/chat/ChatTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.chat; 2 | 3 | import com.africastalking.*; 4 | import com.africastalking.chat.*; 5 | import com.africastalking.test.Fixtures; 6 | import org.junit.Assert; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | 13 | public class ChatTest { 14 | 15 | String productId = "test"; 16 | String channelNumber = "AT2FA"; 17 | 18 | @Before 19 | public void setup() { 20 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 21 | } 22 | 23 | @Test 24 | public void testSendMessage() throws IOException { 25 | ChatService chat = AfricasTalking.getService(AfricasTalking.SERVICE_CHAT); 26 | ChatResponse resp = chat.sendMessage("+254711082302", channelNumber, "hello hello"); 27 | Assert.assertNotNull(resp.messageId); 28 | 29 | resp = chat.sendMessage("+254711082302", channelNumber, MediaMessageBody.MediaType.Image, "https://africastalking.com/fake/media.png"); 30 | Assert.assertNotNull(resp.messageId); 31 | } 32 | 33 | @Test 34 | public void testSendTemplate() throws IOException { 35 | ChatService chat = AfricasTalking.getService(AfricasTalking.SERVICE_CHAT); 36 | ChatResponse resp = chat.sendTemplate("+254711082302", channelNumber, "fake)d", "Test", new ArrayList<>()); 37 | Assert.assertNotNull(resp.messageId); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/GetDigits.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | 5 | public class GetDigits extends Action { 6 | 7 | /** 8 | * Get Digits 9 | * @param say 10 | * @param numDigits 11 | * @param finishOnKey 12 | * @param callbackUrl 13 | */ 14 | public GetDigits(Say say, int numDigits, String finishOnKey, URL callbackUrl) { 15 | init(say, numDigits, finishOnKey, callbackUrl); 16 | } 17 | 18 | public GetDigits(Say say) { 19 | init(say, 0, null, null); 20 | } 21 | 22 | 23 | /** 24 | * Get Digits 25 | * @param play 26 | * @param numDigits 27 | * @param finishOnKey 28 | * @param callbackUrl 29 | */ 30 | public GetDigits(Play play, int numDigits, String finishOnKey, URL callbackUrl) { 31 | init(play, numDigits, finishOnKey, callbackUrl); 32 | } 33 | 34 | public GetDigits(Play play) { 35 | init(play, 0, null, null); 36 | } 37 | 38 | private void init(Action child, int numDigits, String finishOnKey, URL callbackUrl) { 39 | this.tag = "GetDigits"; 40 | 41 | if (numDigits > 0) { 42 | this.attributes.put("numDigits", String.valueOf(numDigits)); 43 | } 44 | 45 | if (finishOnKey != null) { 46 | this.attributes.put("finishOnKey", finishOnKey); 47 | } 48 | 49 | if (callbackUrl != null) { 50 | this.attributes.put("callbackUrl", callbackUrl.toString()); 51 | } 52 | 53 | this.children.add(child); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /libs/core/src/test/resources/cert/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCfpwv6i+ymn+Oz 3 | k2Ii1hHXGD7jYPQ7SuJvFjjWjfbgExMTM33D2olJxyX+eCQIzhKMxJL97oBLlIjT 4 | /A/+oMi8hAW1lMqpEwR1wa9Ntrn1rrLPpMvckQV1/EWaF3eOEa6QJPXzUf/pbemS 5 | ET9cw1Ue4WAqowhONQLznunhjuyqvpc3ZknmQCze+KxsYAUmWatxGw2dBhPC/yuZ 6 | LWLRaCeoX7FxydP3y02bcVRtaoNZK3EJUnnDcXsbgpdP4AB++NjXyW3IvivLHp9d 7 | U2wjVaegwALTq6RM6cyo2TLN3fAtBFk4cikDrlTS7fGkFZCfN0HmJdoPJ25nDyCt 8 | T7OPNYtxAgMBAAECggEAfw7VxK8pRq6G6/qGpozRo+pi2ljl5m5BaQE5eN+As3Ha 9 | w3Q+SemZS0MX5L4+h1dhgfBLv5XZOs1aW8MGA7J4Lzeux4VECGzfJsxfNK0QsAs5 10 | uXFA4Sf1PaHozqqQdltyHrublesTjPXxZlMdpzi5fJsHCse3K26bC8HUBrPVfEhy 11 | g9VfOJiC9UwyMDVh+pVSrS0aNTDD7H0IeUrwZ5Bh9dwFUNLIy8gIhccxdd+dC6gd 12 | SP7tPGqV2Oss6Z6LoqKx0xl5AxxeY5dCUYt1vzVaqFol2i8+LxsDJh1vnB0kjQUd 13 | M41cUNJO7B+wS2vAxZywaYvpnrlFttPYbflKHhZspQKBgQDNFBlapoAaO0f4QVxC 14 | 4vUqS2C1s43RAO/FU5hqz24r4yicTIA0MxA81DTne0+ejwNLxnHn8ZgxLv+/mac4 15 | eTd33lCTGQNSrCHzRX37jt1ImralN3E0AHCwqraymaXde4OYRQ0YACiEJpTdgTm6 16 | J2t0KKBIklQkzwlC+tEkNlxGCwKBgQDHS20UFy9SFuLkq2Cmz8Cn3v7/jBSaWr8X 17 | PMjG5KpZbbX5lOagXA7c3BFtlV45wBMQm1PxJW6wny0t/DndZGPPeF+mWMm8waS7 18 | PvBdcv7Rn36FFfPdrXMQ54J+MZ0syi7tU/UEONGENdSGMZfVOXB4f1jlvpn3eSl1 19 | nRX/twGN8wKBgFzHGAVTyCaV5SRh5Wx5IvMEcPcjCSih5Nd/zzebKjt+pDO5xFJZ 20 | O6JZWqnStp2EE7inSYbmXLT7j0ZOLEG3TSnUkN1Ldp5jM2TMbodtBZ1AKRsrrvIY 21 | tOKoCMlaC5jctRchPiA6ihnnHnfra4NVLB679m9+i8SvSYBc2lsooKwrAoGAeajC 22 | urv4iC0ubxdrVgmG+zN3CPszbJKvCnlxu3DFyPffWa+bJhNsg0jYXsQLFEf7m2ym 23 | zpPiQXG6O9M4Y70v7Ov3v+19oXu4mxGt2+4iPkcWDho7l3yVvKEHGRAiecAm63wQ 24 | G3av9/yAczb9EA4TaYub8gvro5XUSFcPod3O1h8CgYA5rlS61LwhIpXdvinjEupu 25 | Pj+u1b93pPZ1TAaJFiVNeqza/wKVai0pyOhGcyIuHAbJ3WAP7OUc65peA3vsgQ6D 26 | 7U50mCyENum2fYnpvl2MqIwkFqSHL4kMYI5jXRKYeeASVtHheuG5LpmF1ULqlmiQ 27 | N6eJhxMjw02LSlrZM9rp4g== 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /example/src/main/resources/public/css/style.css: -------------------------------------------------------------------------------- 1 | /* Space out content a bit */ 2 | body { 3 | padding-top: 20px; 4 | padding-bottom: 20px; 5 | } 6 | /* Everything but the jumbotron gets side spacing for mobile first views */ 7 | .header, 8 | .marketing, 9 | .footer { 10 | padding-right: 15px; 11 | padding-left: 15px; 12 | } 13 | /* Custom page header */ 14 | .header { 15 | padding-bottom: 20px; 16 | border-bottom: 1px solid #e5e5e5; 17 | } 18 | /* Make the masthead heading the same height as the navigation */ 19 | .header h3 { 20 | margin-top: 0; 21 | margin-bottom: 0; 22 | line-height: 40px; 23 | } 24 | /* Custom page footer */ 25 | .footer { 26 | padding-top: 19px; 27 | color: #777; 28 | } 29 | /* Customize container */ 30 | @media (min-width: 768px) { 31 | /*.container { 32 | max-width: 1200px; 33 | }*/ 34 | } 35 | .container-narrow > hr { 36 | margin: 30px 0; 37 | } 38 | /* Main marketing message and sign up button */ 39 | .jumbotron { 40 | text-align: center; 41 | border-bottom: 1px solid #e5e5e5; 42 | } 43 | /*.jumbotron .btn { 44 | padding: 14px 24px; 45 | font-size: 21px; 46 | }*/ 47 | /* Supporting marketing content */ 48 | .marketing { 49 | margin: 40px 0; 50 | } 51 | .marketing p + h4 { 52 | margin-top: 28px; 53 | } 54 | /* Responsive: Portrait tablets and up */ 55 | @media screen and (min-width: 768px) { 56 | /* Remove the padding we set earlier */ 57 | .header, 58 | .marketing, 59 | .footer { 60 | padding-right: 0; 61 | padding-left: 0; 62 | } 63 | /* Space out the masthead */ 64 | .header { 65 | margin-bottom: 30px; 66 | } 67 | /* Remove the bottom border on the jumbotron for visual effect */ 68 | .jumbotron { 69 | border-bottom: 0; 70 | } 71 | } 72 | 73 | pre { 74 | background: white; 75 | } 76 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/IPremiumSMS.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.sms.FetchSubscriptionResponse; 5 | import com.africastalking.sms.SendMessageResponse; 6 | import com.africastalking.sms.SubscriptionResponse; 7 | import retrofit2.Call; 8 | import retrofit2.http.*; 9 | 10 | interface IPremiumSMS { 11 | 12 | @FormUrlEncoded 13 | @POST("messaging") 14 | Call send(@Field("username") String username, @Field("to") String to, 15 | @Field("from") String from, @Field("message") String message, 16 | @Field("keyword") String keyword, @Field("linkId") String linkId, 17 | @Field("retryDurationInHours") String retryDurationInHours, 18 | @Field("bulkSMSMode") int bulkMode); 19 | 20 | @GET("subscription") 21 | Call fetchSubscriptions(@Query("username") String username, @Query("shortCode") String shortCode, 22 | @Query("keyword") String keyword, @Query("lastReceivedId") long lastReceivedId); 23 | 24 | @FormUrlEncoded 25 | @POST("subscription/create") 26 | Call createSubscription(@Field("username") String username, @Field("shortCode") String shortCode, 27 | @Field("keyword") String keyword, @Field("phoneNumber") String phoneNumber); 28 | 29 | @FormUrlEncoded 30 | @POST("subscription/delete") 31 | Call deleteSubscription(@Field("username") String username, @Field("shortCode") String shortCode, 32 | @Field("keyword") String keyword, @Field("phoneNumber") String phoneNumber); 33 | } 34 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/application/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.application; 2 | 3 | import com.africastalking.*; 4 | import com.africastalking.application.ApplicationResponse; 5 | import com.africastalking.test.Fixtures; 6 | import org.junit.Assert; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.CountDownLatch; 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | 16 | public class ApplicationTest { 17 | 18 | CountDownLatch lock; 19 | 20 | @Before 21 | public void setup() { 22 | lock = new CountDownLatch(10); 23 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 24 | } 25 | 26 | @Test 27 | public void testFetchApplicationData() { 28 | 29 | ApplicationService service = AfricasTalking.getService(ApplicationService.class); 30 | try { 31 | final ApplicationResponse resp = service.fetchApplicationData(); 32 | Assert.assertNotNull(resp); 33 | 34 | service.fetchApplicationData(new Callback() { 35 | @Override 36 | public void onSuccess(ApplicationResponse response) { 37 | Assert.assertNotNull(response); 38 | lock.countDown(); 39 | } 40 | 41 | @Override 42 | public void onFailure(Throwable throwable) { 43 | Assert.fail(throwable.getMessage()); 44 | lock.countDown(); 45 | } 46 | }); 47 | 48 | lock.await(Fixtures.TIMEOUT, TimeUnit.MILLISECONDS); 49 | 50 | } catch (IOException | InterruptedException e) { 51 | Assert.fail(e.getMessage()); 52 | } 53 | 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/IVoice.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.voice.CallResponse; 4 | import com.africastalking.voice.CallTransferResponse; 5 | import com.africastalking.voice.QueuedCallsResponse; 6 | 7 | import retrofit2.Call; 8 | import retrofit2.http.*; 9 | 10 | interface IVoice { 11 | 12 | @FormUrlEncoded 13 | @POST("call") 14 | Call call(@Field("username") String username, @Field("to") String to, 15 | @Field("from") String from); 16 | 17 | @FormUrlEncoded 18 | @POST("callTransfer") 19 | Call callTransfer(@Field("username") String username, @Field("phoneNumber") String phoneNumber, 20 | @Field("sessionId") String sessionId); 21 | 22 | @FormUrlEncoded 23 | @POST("callTransfer") 24 | Call callTransfer(@Field("username") String username, @Field("phoneNumber") String phoneNumber, 25 | @Field("sessionId") String sessionId, @Field("callLeg") String callLeg); 26 | 27 | @FormUrlEncoded 28 | @POST("callTransfer") 29 | Call callTransfer(@Field("username") String username, @Field("phoneNumber") String phoneNumber, 30 | @Field("sessionId") String sessionId, @Field("callLeg") String callLeg, 31 | @Field("holdMusicUrl") String holdMusicUrl); 32 | 33 | @FormUrlEncoded 34 | @POST("queueStatus") 35 | Call queueStatus(@Field("username") String username, @Field("phoneNumbers") String phoneNumbers); 36 | 37 | @FormUrlEncoded 38 | @POST("mediaUpload") 39 | Call mediaUpload(@Field("username") String username, @Field("url") String url, 40 | @Field("phoneNumber") String phoneNumber); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/voice/action/Record.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.voice.action; 2 | 3 | import java.net.URL; 4 | 5 | public class Record extends Action { 6 | 7 | public Record() { 8 | init(null, null, 0, 0, false, false, null); 9 | } 10 | 11 | public Record(String finishOnKey, int maxLength, int timeout, boolean trimSilence, boolean playBeep, URL callbackUrl) { 12 | init(null, finishOnKey, maxLength, timeout, trimSilence, playBeep, callbackUrl); 13 | } 14 | 15 | public Record(Say say, String finishOnKey, int maxLength, int timeout, boolean trimSilence, boolean playBeep, URL callbackUrl) { 16 | init(say, finishOnKey, maxLength, timeout, trimSilence, playBeep, callbackUrl); 17 | } 18 | 19 | public Record(Play play, String finishOnKey, int maxLength, int timeout, boolean trimSilence, boolean playBeep, URL callbackUrl) { 20 | init(play, finishOnKey, maxLength, timeout, trimSilence, playBeep, callbackUrl); 21 | } 22 | 23 | private void init(Action action, String finishOnKey, int maxLength, int timeout, boolean trimSilence, boolean playBeep, URL callbackUrl) { 24 | this.tag = "Record"; 25 | 26 | if (action != null) { 27 | this.children.add(action); 28 | } 29 | 30 | if (finishOnKey != null) { 31 | this.attributes.put("finishOnKey", finishOnKey); 32 | } 33 | if (maxLength > 0) { 34 | this.attributes.put("maxLength", String.valueOf(maxLength)); 35 | } 36 | if (timeout > 0) { 37 | this.attributes.put("timeout", String.valueOf(timeout)); 38 | } 39 | if (trimSilence) { 40 | this.attributes.put("trimSilence", "true"); 41 | } 42 | if (playBeep) { 43 | this.attributes.put("playBeep", "true"); 44 | } 45 | if (callbackUrl != null) { 46 | this.attributes.put("callbackUrl", callbackUrl.toString()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/ActionBuilder.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.voice.action.*; 4 | import com.africastalking.voice.action.Record; 5 | 6 | /** 7 | * Voice Actions builder 8 | */ 9 | public class ActionBuilder { 10 | 11 | private Boolean finalized = false; 12 | private StringBuilder xml; 13 | 14 | public ActionBuilder() { 15 | xml = new StringBuilder(); 16 | xml.append(""); 17 | } 18 | 19 | public String build() { 20 | finalized = true; 21 | xml.append(""); 22 | return xml.toString(); 23 | } 24 | 25 | private ActionBuilder action(Action action) { 26 | if (finalized) throw new RuntimeException("This builder has been finalized by a call to build()"); 27 | xml.append(action.build()); 28 | return this; 29 | } 30 | 31 | public ActionBuilder say(Say action) { 32 | return this.action(action); 33 | } 34 | 35 | public ActionBuilder play(Play action) { 36 | return this.action(action); 37 | } 38 | 39 | public ActionBuilder dial(Dial action) { 40 | return this.action(action); 41 | } 42 | 43 | public ActionBuilder enqueue(Enqueue action) { 44 | return this.action(action); 45 | } 46 | 47 | public ActionBuilder dequeue(Dequeue action) { 48 | return this.action(action); 49 | } 50 | 51 | public ActionBuilder conference(Conference action) { 52 | return this.action(action); 53 | } 54 | 55 | public ActionBuilder redirect(Redirect action) { 56 | return this.action(action); 57 | } 58 | 59 | public ActionBuilder reject(Reject action) { 60 | return this.action(action); 61 | } 62 | 63 | public ActionBuilder record(Record action) { 64 | return this.action(action); 65 | } 66 | 67 | public ActionBuilder getDigits(GetDigits action) { 68 | return this.action(action); 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /example/src/main/resources/public/js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | $(function () { 4 | 5 | 6 | function log(message) { 7 | $("#response").text(message); 8 | $('pre span').each(function(i, block) { 9 | hljs.highlightBlock(block); 10 | }); 11 | } 12 | 13 | $("#signUp").click(() => { 14 | let phone = $("#phone").val(); 15 | if (!phone) { 16 | log(JSON.stringify({ error: "Enter a phone number"}, null, 2)); 17 | return; 18 | } 19 | 20 | log("Sending SMS..."); 21 | 22 | $.ajax({ 23 | type: "POST", 24 | url: `/auth/register/${encodeURIComponent(phone)}`, 25 | success: (resp) => { 26 | try { 27 | log(JSON.stringify(JSON.parse(resp), null, 2)); 28 | } catch (ex) { 29 | log(resp); 30 | } 31 | }, 32 | error: (xhr, textStatus, errorThrown) => { 33 | log(errorThrown); 34 | } 35 | }); 36 | 37 | }); 38 | 39 | $("#airtime").click(() => { 40 | const phone = $("#phone").val(); 41 | const amount = $("#amount").val().split(" "); 42 | if (!phone) { 43 | log(JSON.stringify({ error: "Enter a phone number"}, null, 2)); 44 | return; 45 | } 46 | 47 | if (!amount) { 48 | log(JSON.stringify({ error: "Enter an amount (with currency) e,g, KES 334"}, null, 2)); 49 | return; 50 | } 51 | 52 | log("Sending Airtime..."); 53 | 54 | $.ajax({ 55 | type: "POST", 56 | url: `/airtime/${encodeURIComponent(phone)}?currencyCode=${amount[0]}&amount=${amount[1]}`, 57 | success: (resp) => { 58 | try { 59 | log(JSON.stringify(JSON.parse(resp), null, 2)); 60 | } catch (ex) { 61 | log(resp); 62 | } 63 | }, 64 | error: (xhr, textStatus, errorThrown) => { 65 | log(errorThrown); 66 | } 67 | }); 68 | 69 | }); 70 | }); -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/airtime/AirtimeTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.airtime; 2 | 3 | import com.africastalking.AfricasTalking; 4 | import com.africastalking.AirtimeService; 5 | import com.africastalking.airtime.AirtimeResponse; 6 | import com.africastalking.test.Fixtures; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | 11 | import java.io.IOException; 12 | import java.util.HashMap; 13 | import java.util.concurrent.ThreadLocalRandom; 14 | 15 | 16 | public class AirtimeTest { 17 | 18 | @Before 19 | public void setup() { 20 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 21 | } 22 | 23 | @Test 24 | public void testSendSingle() throws IOException { 25 | AirtimeService service = AfricasTalking.getService(AirtimeService.class); 26 | service.setMaxRetry(5); 27 | AirtimeResponse response = service.send("+254711082302", "KES", ThreadLocalRandom.current().nextInt(10, 10001)); 28 | Assert.assertEquals(1, response.numSent); 29 | } 30 | 31 | @Test 32 | public void testSendMultiple() throws IOException { 33 | AirtimeService service = AfricasTalking.getService(AirtimeService.class); 34 | HashMap people = new HashMap<>(); 35 | people.put("+254731034588", "KES " + ThreadLocalRandom.current().nextInt(500, 5501)); 36 | people.put("+254711082302", "UGX " + ThreadLocalRandom.current().nextInt(3000, 8001)); 37 | AirtimeResponse response = service.send(people); 38 | Assert.assertEquals(2, response.numSent); 39 | } 40 | 41 | @Test 42 | public void testIdempotency() throws IOException { 43 | AirtimeService service = AfricasTalking.getService(AirtimeService.class); 44 | AirtimeResponse response = service.send("+254711082302", "KES", 15); 45 | Assert.assertEquals(1, response.numSent); 46 | response = service.send("+254711082302", "KES", 15); 47 | Assert.assertEquals(0, response.numSent); 48 | service.setIdempotencyKey("rand-0"); 49 | response = service.send("+254711082302", "KES", 15); 50 | Assert.assertEquals(1, response.numSent); 51 | service.setIdempotencyKey("rand-1"); 52 | response = service.send("+254711082302", "KES", 15); 53 | Assert.assertEquals(1, response.numSent); 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/mobileData/MobileDataTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.mobileData; 2 | 3 | import com.africastalking.AfricasTalking; 4 | import com.africastalking.MobileDataService; 5 | import com.africastalking.mobileData.Transaction; 6 | import com.africastalking.mobileData.recipient.MobileDataRecipient; 7 | import com.africastalking.mobileData.response.MobileDataResponse; 8 | import com.africastalking.mobileData.response.WalletBalanceResponse; 9 | import com.africastalking.test.Fixtures; 10 | import org.junit.Assert; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | 14 | import java.io.IOException; 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | public class MobileDataTest { 19 | 20 | @Before 21 | public void setup() { 22 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 23 | } 24 | 25 | @Test 26 | public void testMobileData() throws IOException { 27 | MobileDataService service = AfricasTalking.getService(AfricasTalking.SERVICE_MOBILE_DATA); 28 | MobileDataRecipient recip = new MobileDataRecipient( "+254718769882", 2, MobileDataRecipient.DataUnit.GB, MobileDataRecipient.DataValidity.Month); 29 | List recipients = Arrays.asList(recip); 30 | 31 | MobileDataResponse resp = service.send("TestProduct", recipients); 32 | Assert.assertEquals(1, resp.entries.size()); 33 | } 34 | 35 | @Test 36 | public void testFindTransaction() throws IOException { 37 | MobileDataService service = AfricasTalking.getService(MobileDataService.class); 38 | MobileDataRecipient recip = new MobileDataRecipient( "+254718769882", 15, MobileDataRecipient.DataUnit.MB, MobileDataRecipient.DataValidity.Day); 39 | List recipients = Arrays.asList(recip); 40 | 41 | MobileDataResponse resp = service.send("TestProduct", recipients); 42 | String transactionId = resp.entries.get(0).transactionId; 43 | Transaction transaction = service.findTransaction(transactionId); 44 | Assert.assertEquals(transactionId, transaction.transactionId); 45 | } 46 | 47 | @Test 48 | public void testWalletBalance() throws IOException { 49 | MobileDataService service = AfricasTalking.getService(AfricasTalking.SERVICE_MOBILE_DATA); 50 | WalletBalanceResponse response = service.fetchWalletBalance(); 51 | Assert.assertEquals(true, response.balance.startsWith("KES ")); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /libs/insights/src/main/java/com/africastalking/InsightsService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.insights.SimSwapResponse; 4 | import com.google.gson.Gson; 5 | import retrofit2.Response; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | public final class InsightsService extends Service { 11 | 12 | private InsightsService sInstance; 13 | private IInsights service; 14 | 15 | 16 | 17 | static class SimSwapRequest { 18 | public String username; 19 | public List phoneNumbers; 20 | @Override 21 | public String toString() { 22 | return new Gson().toJson(this); 23 | } 24 | } 25 | 26 | 27 | private InsightsService(String username, String apiKey) { 28 | super(username, apiKey); 29 | 30 | } 31 | 32 | InsightsService() { 33 | super(); 34 | } 35 | 36 | @Override 37 | protected InsightsService getInstance(String username, String apiKey) { 38 | if(sInstance == null) { 39 | sInstance = new InsightsService(username, apiKey); 40 | } 41 | return sInstance; 42 | } 43 | 44 | @Override 45 | protected boolean isInitialized() { 46 | return sInstance != null; 47 | } 48 | 49 | @Override 50 | protected void initService() { 51 | String baseUrl = "https://insights."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN) + "/"; 52 | service = mRetrofitBuilder 53 | .baseUrl(baseUrl) 54 | .build() 55 | .create(IInsights.class); 56 | } 57 | 58 | @Override 59 | protected void destroyService() { 60 | if (sInstance != null) { 61 | sInstance = null; 62 | } 63 | } 64 | 65 | public SimSwapResponse checkSimSwapState(List phoneNumbers) throws IOException { 66 | Response resp = service.checkSimSwapState(_makeRequestBody(phoneNumbers)).execute(); 67 | if(!resp.isSuccessful()) { 68 | throw new IOException(resp.errorBody().toString()); 69 | } 70 | return resp.body(); 71 | } 72 | 73 | public void checkSimSwapSate(List phoneNumbers, Callback callback) { 74 | service.checkSimSwapState(_makeRequestBody(phoneNumbers)).enqueue(makeCallback(callback)); 75 | } 76 | 77 | 78 | private String _makeRequestBody(List phoneNumbers) { 79 | SimSwapRequest req = new SimSwapRequest(); 80 | req.username = mUsername; 81 | req.phoneNumbers = phoneNumbers; 82 | return req.toString(); 83 | } 84 | } -------------------------------------------------------------------------------- /libs/application/src/main/java/com/africastalking/ApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.application.ApplicationResponse; 5 | 6 | import retrofit2.Response; 7 | import retrofit2.Retrofit; 8 | 9 | import java.io.IOException; 10 | 11 | /** 12 | * Account service. Retrieve app info 13 | */ 14 | public class ApplicationService extends Service { 15 | 16 | private static ApplicationService sInstance; 17 | private IApplication service; 18 | 19 | private ApplicationService(String username, String apiKey) { 20 | super(username, apiKey); 21 | } 22 | 23 | ApplicationService() { 24 | super(); 25 | } 26 | 27 | @Override 28 | protected ApplicationService getInstance(String username, String apiKey) { 29 | 30 | if (sInstance == null) { 31 | sInstance = new ApplicationService(username, apiKey); 32 | } 33 | 34 | return sInstance; 35 | } 36 | 37 | @Override 38 | protected void initService() { 39 | String url = "https://api."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN); 40 | url += "/version1/"; 41 | Retrofit retrofit = mRetrofitBuilder 42 | .baseUrl(url) 43 | .build(); 44 | 45 | service = retrofit.create(IApplication.class); 46 | } 47 | 48 | @Override 49 | protected boolean isInitialized() { 50 | return sInstance != null; 51 | } 52 | 53 | @Override 54 | protected void destroyService() { 55 | if (sInstance != null) { 56 | sInstance = null; 57 | } 58 | } 59 | 60 | 61 | // -> 62 | 63 | /** 64 | * Get app info. 65 | *

66 | * Synchronously send the request and return its response. 67 | *

68 | * @return String in specified format, xml or json 69 | * @throws IOException 70 | */ 71 | public ApplicationResponse fetchApplicationData() throws IOException { 72 | Response resp = service.fetchApplicationData(mUsername).execute(); 73 | if (!resp.isSuccessful()) { 74 | throw new IOException(resp.errorBody().string()); 75 | } 76 | return resp.body(); 77 | } 78 | 79 | /** 80 | * Get app info. 81 | *

82 | * Asynchronously send the request and notify {@code callback} of its response or if an error 83 | * occurred 84 | *

85 | * @param callback {@link com.africastalking.Callback Callback} 86 | */ 87 | public void fetchApplicationData(final Callback callback) { 88 | service.fetchApplicationData(mUsername).enqueue(makeCallback(callback)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/src/main/resources/views/index.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Africa's Talking 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 27 |

Africa's Talking

28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 |

Try clicking some buttons

39 |
40 | 41 |
42 |

43 | Send SMS 44 |

45 |
46 | 47 |
48 |

49 | Airtime 50 |

51 |
52 | 53 |
54 |

Read this for more info on all available features

55 |
56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/chat/InteractiveMessageBody.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.chat; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class InteractiveMessageBody extends MessageBody { 7 | 8 | public InteractiveAction action; 9 | public InteractiveText body = null; 10 | public InteractiveText header = null; 11 | public InteractiveText footer = null; 12 | 13 | public static class InteractiveText { 14 | public String text; 15 | public InteractiveText(String text) { 16 | this.text = text; 17 | } 18 | } 19 | 20 | public interface InteractiveAction {} 21 | 22 | public static class InteractiveList implements InteractiveAction { 23 | public String button; 24 | public List
sections; 25 | 26 | public static class Section { 27 | public String title; 28 | public List rows; 29 | public List product_items; 30 | 31 | public static class SectionRow { 32 | public String id; 33 | public String title; 34 | public String description = null; 35 | } 36 | 37 | public static class SectionProductItem { 38 | public String product_retailer_id; 39 | } 40 | } 41 | } 42 | 43 | public static class ReplyButtons implements InteractiveAction { 44 | public List buttons; 45 | 46 | public static class ReplyButton { 47 | public String id; 48 | public String title; 49 | } 50 | } 51 | 52 | public static class InteractiveProduct implements InteractiveAction { 53 | public String catalog_id; 54 | public String product_retailer_id; 55 | } 56 | 57 | public static class InteractiveProductList implements InteractiveAction { 58 | public String catalog_id; 59 | public String product_retailer_id; 60 | public List sections; 61 | } 62 | 63 | public static class InteractiveFlow implements InteractiveAction { 64 | public String flow_message_version; 65 | public String flow_token; 66 | public String flow_id; 67 | public String flow_cta; 68 | public String flow_action = null; 69 | public FlowMode mode; 70 | public FlowActionPayload flow_action_payload = null; 71 | 72 | public enum FlowMode{ 73 | Draft, Published 74 | } 75 | public static class FlowActionPayload { 76 | public String screen; 77 | public Map data = null; 78 | } 79 | } 80 | 81 | public InteractiveMessageBody(InteractiveAction action) { 82 | this.action = action; 83 | } 84 | 85 | public InteractiveMessageBody(InteractiveAction action, InteractiveText body) { 86 | this(action); 87 | this.body = body; 88 | } 89 | 90 | public InteractiveMessageBody(InteractiveAction action, InteractiveText body, InteractiveText header) { 91 | this(action, body); 92 | this.header = header; 93 | } 94 | 95 | public InteractiveMessageBody(InteractiveAction action, InteractiveText body, InteractiveText header, InteractiveText footer) { 96 | this(action, body, header); 97 | this.footer = footer; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/Service.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import okhttp3.*; 4 | import okhttp3.logging.HttpLoggingInterceptor; 5 | import retrofit2.*; 6 | import retrofit2.Call; 7 | import retrofit2.converter.gson.GsonConverterFactory; 8 | import retrofit2.converter.scalars.ScalarsConverterFactory; 9 | 10 | import java.io.IOException; 11 | 12 | 13 | /** 14 | * A given service offered by AT API 15 | */ 16 | abstract class Service { 17 | 18 | Retrofit.Builder mRetrofitBuilder; 19 | String mUsername; 20 | 21 | protected String mIdempotencyKey = null; 22 | 23 | static boolean isSandbox = false; 24 | static Logger LOGGER = null; 25 | 26 | Service(final String username, final String apiKey) { 27 | 28 | mUsername = username; 29 | 30 | OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); 31 | 32 | httpClient.addInterceptor(chain -> { 33 | Request original = chain.request(); 34 | HttpUrl url = original.url(); 35 | if (AfricasTalking.hostOverride != null) { 36 | url = url.newBuilder() 37 | .host(AfricasTalking.hostOverride) 38 | .build(); 39 | } 40 | Request.Builder builder = original.newBuilder() 41 | .url(url) 42 | .addHeader("apiKey", apiKey) 43 | .addHeader("Accept", "application/json"); 44 | 45 | if (mIdempotencyKey != null) { 46 | builder.addHeader("Idempotency-Key", mIdempotencyKey); 47 | mIdempotencyKey = null; 48 | } 49 | 50 | return chain.proceed(builder.build()); 51 | }); 52 | 53 | if (LOGGER != null) { 54 | HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { 55 | @Override 56 | public void log(String message) { 57 | LOGGER.log(message); 58 | } 59 | }); 60 | logger.setLevel(HttpLoggingInterceptor.Level.BODY); 61 | httpClient.addInterceptor(logger); 62 | } 63 | 64 | mRetrofitBuilder = new Retrofit.Builder() 65 | .addConverterFactory(ScalarsConverterFactory.create()) 66 | .addConverterFactory(GsonConverterFactory.create()) 67 | .client(httpClient.build()); 68 | 69 | initService(); 70 | } 71 | 72 | Service() {} 73 | 74 | 75 | /** 76 | * 77 | * @param cb 78 | * @param 79 | * @return 80 | */ 81 | protected retrofit2.Callback makeCallback(final Callback cb) { 82 | return new retrofit2.Callback() { 83 | @Override 84 | public void onResponse(Call call, retrofit2.Response response) { 85 | if (response.isSuccessful()) { 86 | cb.onSuccess(response.body()); 87 | } else { 88 | cb.onFailure(new Exception(response.message())); 89 | } 90 | } 91 | 92 | @Override 93 | public void onFailure(Call call, Throwable t) { 94 | cb.onFailure(t); 95 | } 96 | }; 97 | } 98 | 99 | /** 100 | * Get an instance of a service. 101 | * @param username 102 | * @param apiKey 103 | * @param 104 | * @return 105 | */ 106 | protected abstract T getInstance(String username, String apiKey); 107 | 108 | /** 109 | * Check if a service is initialized 110 | * @return boolean true if yes, false otherwise 111 | */ 112 | protected abstract boolean isInitialized(); 113 | 114 | /** 115 | * Initializes a service 116 | */ 117 | protected abstract void initService(); 118 | 119 | /** 120 | * Destroys a service 121 | */ 122 | protected abstract void destroyService(); 123 | 124 | protected boolean checkPhoneNumber(String phone) throws IOException { 125 | if (!phone.matches(Const.INTL_PHONE_FORMAT)) { 126 | throw new IOException("Invalid phone number: " + phone + "; Expecting number in format +XXXxxxxxxxxx"); 127 | } 128 | return true; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/voice/VoiceTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.voice; 2 | 3 | import com.africastalking.*; 4 | import com.africastalking.test.Fixtures; 5 | import com.africastalking.voice.CallResponse; 6 | import com.africastalking.voice.CallTransferResponse; 7 | import com.africastalking.voice.QueuedCallsResponse; 8 | 9 | import com.africastalking.voice.action.GetDigits; 10 | import com.africastalking.voice.action.Record; 11 | import com.africastalking.voice.action.Say; 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.io.IOException; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | 20 | public class VoiceTest { 21 | 22 | 23 | @Before 24 | public void setup() { 25 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 26 | } 27 | 28 | /* 29 | 30 | TODO: Voice Sandbox not available atm - reactivate when it's back 31 | 32 | @Test 33 | public void testCall() { 34 | VoiceService service = AfricasTalking.getService(VoiceService.class); 35 | try { 36 | CallResponse response = service.call("+254718769882", "0718769881"); 37 | Assert.assertEquals("Invalid callerId: 0718769881", response.errorMessage); 38 | } catch (IOException e) { 39 | Assert.fail(e.getMessage()); 40 | } 41 | } 42 | 43 | @Test 44 | public void testCallTransfer() { 45 | VoiceService service = AfricasTalking.getService(VoiceService.class); 46 | try { 47 | CallTransferResponse response = service.callTransfer("+254718769882", "session-id"); 48 | Assert.assertEquals("Invalid callerId: 0718769881", response.errorMessage); 49 | 50 | } catch (IOException e) { 51 | Assert.fail(e.getMessage()); 52 | } 53 | } 54 | 55 | @Test 56 | public void testFetchQueuedCalls() { 57 | VoiceService service = AfricasTalking.getService(VoiceService.class); 58 | try { 59 | QueuedCallsResponse response = service.fetchQueuedCalls("0718769882"); 60 | Assert.assertEquals(0, response.entries.size()); 61 | 62 | } catch (IOException e) { 63 | Assert.fail(e.getMessage()); 64 | } 65 | } 66 | 67 | @Test 68 | public void testUploadMediaFile() { 69 | VoiceService service = AfricasTalking.getService(VoiceService.class); 70 | try { 71 | final String response = service.uploadMediaFile("+254718769889", "http://defef.klo/wave.mp3"); 72 | Assert.assertNotNull(response); 73 | } catch (IOException e) { 74 | Assert.fail(e.getMessage()); 75 | } 76 | } 77 | */ 78 | 79 | @Test 80 | public void testXmlBuilder() { 81 | ActionBuilder builder = new ActionBuilder(); 82 | 83 | String say = "Your balance is 1234 Shillings"; 84 | Assert.assertEquals(say, builder.say(new Say("Your balance is 1234 Shillings", false, Say.Voice.MAN)).build()); 85 | 86 | builder = new ActionBuilder(); 87 | 88 | String getDigits = "" + 89 | "" + 90 | "" + 91 | "Please enter your account number followed by the hash sign" + 92 | "" + 93 | "" + 94 | ""; 95 | String getDigitsTest = builder 96 | .getDigits(new GetDigits( 97 | new Say("Please enter your account number followed by the hash sign"), 98 | 0, 99 | "#", 100 | null)) 101 | .record(new Record()) 102 | .build(); 103 | Assert.assertEquals(getDigits, getDigitsTest); 104 | 105 | builder = new ActionBuilder(); 106 | String record = "" + 107 | "" + 108 | "" + 109 | "Please say your name after the beep." + 110 | "" + 111 | ""; 112 | String recordTest = builder 113 | .record( 114 | new Record( 115 | new Say("Please say your name after the beep."), 116 | "*", 117 | 10, 118 | 0, 119 | true, 120 | true, 121 | null 122 | ) 123 | ) 124 | .build(); 125 | Assert.assertEquals(record, recordTest); 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 4 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 5 | 6 | # User-specific stuff: 7 | .idea/ 8 | 9 | ## File-based project format: 10 | *.iws 11 | 12 | ## Plugin-specific files: 13 | 14 | # IntelliJ 15 | /out/ 16 | out/ 17 | gen/ 18 | 19 | # mpeltonen/sbt-idea plugin 20 | .idea_modules/ 21 | 22 | # JIRA plugin 23 | atlassian-ide-plugin.xml 24 | 25 | # Crashlytics plugin (for Android Studio and IntelliJ) 26 | com_crashlytics_export_strings.xml 27 | crashlytics.properties 28 | crashlytics-build.properties 29 | fabric.properties 30 | ### Java template 31 | *.class 32 | 33 | # Mobile Tools for Java (J2ME) 34 | .mtj.tmp/ 35 | 36 | # Package Files # 37 | *.jar 38 | *.war 39 | *.ear 40 | 41 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 42 | hs_err_pid* 43 | ### Gradle template 44 | .gradle 45 | build/ 46 | 47 | # Ignore Gradle GUI config 48 | gradle-app.setting 49 | 50 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 51 | !gradle-wrapper.jar 52 | doc/javadoc/ 53 | .sandbox.json 54 | *.iml 55 | 56 | # Cache of project 57 | .gradletasknamecache 58 | 59 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 60 | # gradle/wrapper/gradle-wrapper.properties 61 | 62 | # # 63 | local.properties 64 | .vscode/ 65 | .settings/ 66 | .project 67 | .classpath 68 | bin/ 69 | ### Linux template 70 | *~ 71 | 72 | # temporary files which can be created if a process still has a handle open of a deleted file 73 | .fuse_hidden* 74 | 75 | # KDE directory preferences 76 | .directory 77 | 78 | # Linux trash folder which might appear on any partition or disk 79 | .Trash-* 80 | 81 | # .nfs files are created when an open file is removed but is still being accessed 82 | .nfs* 83 | 84 | ### Gradle template 85 | /build/ 86 | 87 | # Ignore Gradle GUI config 88 | 89 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 90 | 91 | # Cache of project 92 | 93 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 94 | # gradle/wrapper/gradle-wrapper.properties 95 | 96 | ### macOS template 97 | # General 98 | .DS_Store 99 | .AppleDouble 100 | .LSOverride 101 | 102 | # Icon must end with two \r 103 | Icon 104 | 105 | # Thumbnails 106 | ._* 107 | 108 | # Files that might appear in the root of a volume 109 | .DocumentRevisions-V100 110 | .fseventsd 111 | .Spotlight-V100 112 | .TemporaryItems 113 | .Trashes 114 | .VolumeIcon.icns 115 | .com.apple.timemachine.donotpresent 116 | 117 | # Directories potentially created on remote AFP share 118 | .AppleDB 119 | .AppleDesktop 120 | Network Trash Folder 121 | Temporary Items 122 | .apdisk 123 | 124 | ### Kotlin template 125 | # Compiled class file 126 | 127 | # Log file 128 | *.log 129 | 130 | # BlueJ files 131 | *.ctxt 132 | 133 | # Mobile Tools for Java (J2ME) 134 | 135 | # Package Files # 136 | *.nar 137 | *.zip 138 | *.tar.gz 139 | *.rar 140 | 141 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 142 | 143 | ### JetBrains template 144 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 145 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 146 | 147 | # User-specific stuff 148 | .idea/**/workspace.xml 149 | .idea/**/tasks.xml 150 | .idea/**/usage.statistics.xml 151 | .idea/**/dictionaries 152 | .idea/**/shelf 153 | 154 | # Generated files 155 | .idea/**/contentModel.xml 156 | 157 | # Sensitive or high-churn files 158 | .idea/**/dataSources/ 159 | .idea/**/dataSources.ids 160 | .idea/**/dataSources.local.xml 161 | .idea/**/sqlDataSources.xml 162 | .idea/**/dynamic.xml 163 | .idea/**/uiDesigner.xml 164 | .idea/**/dbnavigator.xml 165 | 166 | # Gradle 167 | .idea/**/gradle.xml 168 | .idea/**/libraries 169 | 170 | # Gradle and Maven with auto-import 171 | # When using Gradle or Maven with auto-import, you should exclude module files, 172 | # since they will be recreated, and may cause churn. Uncomment if using 173 | # auto-import. 174 | # .idea/modules.xml 175 | # .idea/*.iml 176 | # .idea/modules 177 | # *.iml 178 | # *.ipr 179 | 180 | # CMake 181 | cmake-build-*/ 182 | 183 | # Mongo Explorer plugin 184 | .idea/**/mongoSettings.xml 185 | 186 | # File-based project format 187 | 188 | # IntelliJ 189 | 190 | # mpeltonen/sbt-idea plugin 191 | 192 | # JIRA plugin 193 | 194 | # Cursive Clojure plugin 195 | .idea/replstate.xml 196 | 197 | # Crashlytics plugin (for Android Studio and IntelliJ) 198 | 199 | # Editor-based Rest Client 200 | .idea/httpRequests 201 | 202 | # Android studio 3.1+ serialized cache file 203 | .idea/caches/build_file_checksums.ser 204 | 205 | ### Java template 206 | # Compiled class file 207 | 208 | # Log file 209 | 210 | # BlueJ files 211 | 212 | # Mobile Tools for Java (J2ME) 213 | 214 | # Package Files # 215 | 216 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 217 | -------------------------------------------------------------------------------- /libs/common/src/main/java/com/africastalking/AfricasTalking.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | public final class AfricasTalking { 4 | 5 | private static final String BASE_PACKAGE = AfricasTalking.class.getPackage().getName(); 6 | 7 | public static final String SERVICE_APPLICATION = BASE_PACKAGE + ".ApplicationService"; 8 | public static final String SERVICE_SMS = BASE_PACKAGE + ".SmsService"; 9 | public static final String SERVICE_CHAT = BASE_PACKAGE + ".ChatService"; 10 | public static final String SERVICE_VOICE = BASE_PACKAGE + ".VoiceService"; 11 | public static final String SERVICE_USSD = BASE_PACKAGE + ".UssdService"; 12 | public static final String SERVICE_AIRTIME = BASE_PACKAGE + ".AirtimeService"; 13 | public static final String SERVICE_PAYMENT = BASE_PACKAGE + ".PaymentService"; 14 | public static final String SERVICE_MOBILE_DATA = BASE_PACKAGE + ".MobileDataService"; 15 | public static final String SERVICE_INSIGHTS = BASE_PACKAGE + ".InsightsService"; 16 | 17 | 18 | @Deprecated 19 | public static String hostOverride = null; 20 | 21 | static String sUsername, sApiKey; 22 | 23 | 24 | /** 25 | * Initialize the SDK 26 | * @param username app username 27 | * @param apiKey app API key 28 | */ 29 | public static void initialize(String username, String apiKey){ 30 | 31 | destroyAllServices(); 32 | 33 | // Init 34 | sUsername = username; 35 | sApiKey = apiKey; 36 | Service.isSandbox = username.toLowerCase().contentEquals("sandbox"); 37 | } 38 | 39 | /** 40 | * Set request logger 41 | * @param logger logger object 42 | */ 43 | public static void setLogger(Logger logger) { 44 | Service.LOGGER = logger; 45 | } 46 | 47 | /** 48 | * Get a service by class. e.g. AirtimeService.class 49 | * @param classInfo service class 50 | * @param service type 51 | * @return An instance of the requested service 52 | */ 53 | public static T getService(Class classInfo) { 54 | try { 55 | T raw = classInfo.newInstance(); 56 | 57 | if (sApiKey == null || sUsername == null) { 58 | return raw; 59 | } 60 | return (T)raw.getInstance(sUsername, sApiKey); 61 | } catch (InstantiationException e) { 62 | e.printStackTrace(); 63 | } catch (IllegalAccessException e) { 64 | e.printStackTrace(); 65 | } 66 | 67 | throw new RuntimeException("Failed to init service"); 68 | } 69 | 70 | /** 71 | * Get a service by class. e.g. AirtimeService.class using a specific username and apiKey 72 | * 73 | * @param classInfo service class 74 | * @param service type 75 | * @param username 76 | * @param apiKey 77 | * @return An instance of the requested service 78 | */ 79 | public static T getService(Class classInfo, String username, String apiKey) { 80 | try { 81 | T raw = classInfo.newInstance(); 82 | 83 | if (username == null || apiKey == null) { 84 | return raw; 85 | } 86 | return (T)raw.getInstance(username, apiKey); 87 | } catch (InstantiationException e) { 88 | e.printStackTrace(); 89 | } catch (IllegalAccessException e) { 90 | e.printStackTrace(); 91 | } 92 | 93 | throw new RuntimeException("Failed to init service"); 94 | } 95 | 96 | /** 97 | * Get a service by name using a specific username and apiKey 98 | * @param serviceName see AfricasTalking.SERVICES_* 99 | * @param username 100 | * @param apiKey 101 | * @return An instance of the requested service 102 | */ 103 | public static T getService(String serviceName, String username, String apiKey) { 104 | 105 | try { 106 | Class tClass = (Class)Class.forName(serviceName); 107 | return getService(tClass, username, apiKey); 108 | } catch (ClassNotFoundException e) { 109 | //e.printStackTrace(); 110 | return null; 111 | } 112 | } 113 | 114 | /** 115 | * Get a service by name 116 | * @param serviceName see AfricasTalking.SERVICES_* 117 | * @param service type 118 | * @return An instance of the requested service 119 | */ 120 | public static T getService(String serviceName) { 121 | 122 | try { 123 | Class tClass = (Class)Class.forName(serviceName); 124 | return getService(tClass); 125 | } catch (ClassNotFoundException e) { 126 | //e.printStackTrace(); 127 | return null; 128 | } 129 | } 130 | 131 | /** 132 | * Destroy all initialized services 133 | */ 134 | private static void destroyAllServices() { 135 | Service services[] = new Service[] { 136 | getService(SERVICE_APPLICATION), 137 | getService(SERVICE_VOICE), 138 | getService(SERVICE_SMS), 139 | getService(SERVICE_USSD), 140 | getService(SERVICE_AIRTIME), 141 | getService(SERVICE_PAYMENT), 142 | getService(SERVICE_CHAT), 143 | getService(SERVICE_INSIGHTS) 144 | }; 145 | for (Service service:services) { 146 | if (service != null && service.isInitialized()) { 147 | service.destroyService(); 148 | } 149 | } 150 | } 151 | 152 | } -------------------------------------------------------------------------------- /libs/core/src/test/java/com/africastalking/test/sms/SmsTest.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.test.sms; 2 | 3 | import com.africastalking.AfricasTalking; 4 | import com.africastalking.Callback; 5 | import com.africastalking.SmsService; 6 | import com.africastalking.Status; 7 | import com.africastalking.sms.Message; 8 | import com.africastalking.sms.Recipient; 9 | import com.africastalking.sms.Subscription; 10 | import com.africastalking.sms.SubscriptionResponse; 11 | import com.africastalking.test.Fixtures; 12 | import org.junit.Assert; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.io.IOException; 17 | import java.util.List; 18 | import java.util.concurrent.ThreadLocalRandom; 19 | import java.util.concurrent.CountDownLatch; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | public class SmsTest { 23 | 24 | @Before 25 | public void setup() { 26 | AfricasTalking.initialize(Fixtures.USERNAME, Fixtures.API_KEY); 27 | } 28 | 29 | @Test 30 | public void testSendHighMultipleVolume() throws IOException, InterruptedException { 31 | int count = 100000; 32 | CountDownLatch lock = new CountDownLatch(count); 33 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 34 | for (int i = 0; i < count; i++) { 35 | int offset = count + i; 36 | String[] numbers = new String[] { "+254711" + offset }; 37 | sms.send("testSendHighMultipleVolume(" + numbers[0] + ")", "AT2FA", numbers, true, new Callback>() { 38 | @Override 39 | public void onSuccess(List response) { 40 | Assert.assertNotNull(response); 41 | lock.countDown(); 42 | } 43 | 44 | @Override 45 | public void onFailure(Throwable throwable) { 46 | Assert.fail(throwable.getMessage()); 47 | lock.countDown(); 48 | } 49 | }); 50 | } 51 | lock.await(Fixtures.TIMEOUT, TimeUnit.MILLISECONDS); 52 | } 53 | 54 | @Test 55 | public void testSendHighSingleVolume() throws IOException, InterruptedException { 56 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 57 | 58 | int count = 100000; 59 | CountDownLatch lock = new CountDownLatch(1); 60 | 61 | String[] numbers = new String[count]; 62 | for (int i = 0; i < count; i++) { 63 | int offset = count + i; 64 | numbers[i] = "+254711" + offset; 65 | } 66 | sms.send("testSendHighSingleVolume()", "AT2FA", numbers, true, new Callback>() { 67 | @Override 68 | public void onSuccess(List response) { 69 | System.out.println(response.get(0).toString()); 70 | Assert.assertNotNull(response); 71 | lock.countDown(); 72 | } 73 | 74 | @Override 75 | public void onFailure(Throwable throwable) { 76 | Assert.fail(throwable.getMessage()); 77 | lock.countDown(); 78 | } 79 | }); 80 | 81 | lock.await(Fixtures.TIMEOUT, TimeUnit.MILLISECONDS); 82 | } 83 | 84 | @Test 85 | public void testSend() throws IOException { 86 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 87 | List resp = sms.send("testSend()", "AT2FA", new String[] {"+254711082302", "+254731034588"}, false); 88 | Assert.assertEquals(2, resp.size()); 89 | Assert.assertEquals(Status.SUCCESS, resp.get(0).status); 90 | Assert.assertEquals(101, resp.get(0).statusCode); 91 | } 92 | 93 | @Test 94 | public void testSendPremium() throws IOException { 95 | 96 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 97 | List resp = sms.sendPremium("testSendPremium()", "8989", "KiKi", "Linky", 10, new String[] {"+254711082302", "+254731034588"}); 98 | Assert.assertEquals(2, resp.size()); 99 | Assert.assertEquals(Status.SUCCESS, resp.get(0).status); 100 | 101 | } 102 | 103 | @Test 104 | public void testFetchMessages() throws IOException { 105 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 106 | List resp = sms.fetchMessages(0); 107 | Assert.assertEquals(true, resp.size() >= 0); 108 | } 109 | 110 | @Test 111 | public void testFetchSubscriptions() throws IOException { 112 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 113 | List resp = sms.fetchSubscriptions("AT2FA", "KiKi", 0); 114 | Assert.assertEquals(true, resp.size() >= 0); 115 | } 116 | 117 | @Test 118 | public void testCreateSubscription() throws IOException { 119 | String phone = "+254731034" + ThreadLocalRandom.current().nextInt(100, 999); 120 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 121 | 122 | SubscriptionResponse resp = sms.createSubscription("AT2FA", "KiKi", phone); 123 | Assert.assertEquals("Invalid token provided", resp.description); 124 | } 125 | 126 | @Test 127 | public void testDeleteSubscription() throws IOException { 128 | String phone = "+254731034" + ThreadLocalRandom.current().nextInt(100, 999); 129 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 130 | SubscriptionResponse resp = sms.deleteSubscription("AT2FA", "KiKi", phone); 131 | Assert.assertEquals("Success", resp.status); 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /libs/airtime/src/main/java/com/africastalking/AirtimeService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.airtime.AirtimeResponse; 5 | 6 | import retrofit2.Response; 7 | import retrofit2.Retrofit; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | 12 | 13 | /** 14 | * Airtime Service; send airtime 15 | */ 16 | public final class AirtimeService extends Service { 17 | 18 | 19 | private static AirtimeService sInstance; 20 | private IAirtime service; 21 | private int mMaxNumRetry = 0; 22 | 23 | private AirtimeService(String username, String apiKey) { 24 | super(username, apiKey); 25 | } 26 | 27 | AirtimeService() { 28 | super(); 29 | } 30 | 31 | @Override 32 | protected AirtimeService getInstance(String username, String apiKey) { 33 | 34 | if (sInstance == null) { 35 | sInstance = new AirtimeService(username, apiKey); 36 | } 37 | 38 | return sInstance; 39 | } 40 | 41 | @Override 42 | protected void initService() { 43 | String url = "https://api."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN); 44 | url += "/version1/airtime/"; 45 | Retrofit retrofit = mRetrofitBuilder 46 | .baseUrl(url) 47 | .build(); 48 | 49 | service = retrofit.create(IAirtime.class); 50 | } 51 | 52 | @Override 53 | protected boolean isInitialized() { 54 | return sInstance != null; 55 | } 56 | 57 | @Override 58 | protected void destroyService() { 59 | if (sInstance != null) { 60 | sInstance = null; 61 | } 62 | } 63 | 64 | 65 | 66 | public void setIdempotencyKey(String key) { 67 | this.mIdempotencyKey = key; 68 | } 69 | 70 | 71 | /** 72 | * Set the maximum number of retries in case of failed airtime deliveries due to telco unavailability or any other reason. 73 | * @param retires 74 | */ 75 | public void setMaxRetry(int retires) { 76 | this.mMaxNumRetry = retires; 77 | } 78 | 79 | 80 | /** 81 | * Send airtime 82 | *

83 | * Synchronously send the request and return its response. 84 | *

85 | * @param phone Phone number (international format) to receive the airtime 86 | * @param currencyCode 87 | * @param amount Amount to send 88 | * @return {@link com.africastalking.airtime.AirtimeResponse AirtimeResponse} 89 | * @throws IOException 90 | */ 91 | public AirtimeResponse send(String phone, String currencyCode, float amount) throws IOException { 92 | HashMap map = new HashMap<>(); 93 | map.put(phone, currencyCode + " " + amount); 94 | return send(map); 95 | } 96 | 97 | /** 98 | * Send airtime 99 | *

100 | * Asynchronously send the request and notify {@code callback} of its response or if an error 101 | * occurred 102 | *

103 | * @param phone Phone number (international format) to receive the airtime 104 | * @param currencyCode 105 | * @param amount Amount to send 106 | * @param callback {@link com.africastalking.Callback Callback} to be called once a response is received 107 | */ 108 | public void send(String phone, String currencyCode, float amount, Callback callback) { 109 | HashMap map = new HashMap<>(); 110 | map.put(phone, currencyCode + " " + amount); 111 | send(map, callback); 112 | } 113 | 114 | /** 115 | * Send airtime 116 | *

117 | * Synchronously send the request and return its response. 118 | *

119 | * @param recipients Phone numbers with amounts to send e.g. UGX 854 120 | * @return {@link com.africastalking.airtime.AirtimeResponse AirtimeResponse} 121 | * @throws IOException 122 | */ 123 | public AirtimeResponse send(HashMap recipients) throws IOException { 124 | String json = _makeRecipientsJSON(recipients); 125 | Response resp = service.send(mUsername, json, mMaxNumRetry > 0 ? String.valueOf(mMaxNumRetry) : null).execute(); 126 | if (!resp.isSuccessful()) { 127 | throw new IOException(resp.errorBody().string()); 128 | } 129 | return resp.body(); 130 | } 131 | 132 | /** 133 | * Send airtime 134 | *

135 | * Asynchronously send the request and notify {@code callback} of its response or if an error 136 | * occurred 137 | *

138 | * @param recipients Phone numbers with amounts to send e.g. UGX 854 139 | * @param callback {@link com.africastalking.Callback Callback} to be called once a response is received 140 | */ 141 | public void send(HashMap recipients, Callback callback) { 142 | try{ 143 | String json = _makeRecipientsJSON(recipients); 144 | service.send(mUsername, json, mMaxNumRetry > 0 ? String.valueOf(mMaxNumRetry) : null).enqueue(makeCallback(callback)); 145 | }catch (IOException ioe) { 146 | callback.onFailure(ioe); 147 | } 148 | } 149 | 150 | 151 | private String _makeRecipientsJSON(HashMap recipients) throws IOException { 152 | 153 | if (recipients == null || recipients.size() == 0) { 154 | throw new IOException("Invalid recipients"); 155 | } 156 | 157 | StringBuilder body = new StringBuilder(); 158 | int count = recipients.size(); 159 | for (String phone:recipients.keySet()) { 160 | checkPhoneNumber(phone); 161 | String amount = recipients.get(phone); 162 | String target = "{\"phoneNumber\":\"" + phone + "\", \"amount\": \""+ amount +"\"}"; 163 | body.append(target); 164 | 165 | if (count > 1) { 166 | body.append(","); 167 | } 168 | count--; 169 | } 170 | 171 | return "[" + body.toString() + "]"; 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /libs/chat/src/main/java/com/africastalking/ChatService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.chat.*; 5 | import retrofit2.Response; 6 | 7 | import java.io.IOException; 8 | import java.util.List; 9 | 10 | /** 11 | * Chat Service; 12 | */ 13 | public final class ChatService extends Service { 14 | 15 | private static ChatService sInstance; 16 | private IChat chat; 17 | 18 | private ChatService(String username, String apiKey) { 19 | super(username, apiKey); 20 | } 21 | 22 | ChatService() { 23 | super(); 24 | } 25 | 26 | @Override 27 | protected ChatService getInstance(String username, String apiKey) { 28 | 29 | if (sInstance == null) { 30 | sInstance = new ChatService(username, apiKey); 31 | } 32 | 33 | return sInstance; 34 | } 35 | 36 | @Override 37 | protected void initService() { 38 | String baseUrl = "https://chat."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN); 39 | chat = mRetrofitBuilder.baseUrl(baseUrl).build().create(IChat.class); 40 | } 41 | 42 | @Override 43 | protected boolean isInitialized() { 44 | return sInstance != null; 45 | } 46 | 47 | @Override 48 | protected void destroyService() { 49 | if (sInstance != null) { 50 | sInstance = null; 51 | } 52 | } 53 | 54 | 55 | /* -> Message & Template <- */ 56 | 57 | 58 | /** 59 | * Send a message 60 | *

61 | * Synchronously send the request and return its response. 62 | *

63 | * @param customerNumber 64 | * @param channelNumber - e.g. WhatsApp number 65 | * @param body 66 | * @return 67 | * @throws IOException 68 | */ 69 | public ChatResponse sendMessage(String customerNumber, String channelNumber, MessageBody body) throws IOException { 70 | ChatMessage message = new ChatMessage(); 71 | message.body = body; 72 | message.username = mUsername; 73 | message.waNumber = channelNumber; 74 | message.phoneNumber = customerNumber; 75 | Response resp = chat.sendMessage(message).execute(); 76 | if (!resp.isSuccessful()) { 77 | throw new IOException(resp.errorBody().string()); 78 | } 79 | return resp.body(); 80 | } 81 | 82 | 83 | /** 84 | * Send a message with text 85 | * @param customerNumber 86 | * @param channelNumber 87 | * @param text 88 | * @return 89 | * @throws IOException 90 | */ 91 | public ChatResponse sendMessage(String customerNumber, String channelNumber, String text) throws IOException { 92 | return sendMessage(customerNumber, channelNumber, new TextMessageBody(text)); 93 | } 94 | 95 | /** 96 | * Send a message with media 97 | * @param customerNumber 98 | * @param channelNumber 99 | * @param mediaType 100 | * @param url 101 | * @return 102 | * @throws IOException 103 | */ 104 | public ChatResponse sendMessage(String customerNumber, String channelNumber, MediaMessageBody.MediaType mediaType, String url) throws IOException { 105 | return sendMessage(customerNumber, channelNumber, new MediaMessageBody(mediaType, url)); 106 | } 107 | 108 | /** 109 | * Send a template 110 | *

111 | * Synchronously send the request and return its response. 112 | *

113 | * @param customerNumber 114 | * @param channelNumber 115 | * @param templateId 116 | * @param header 117 | * @param body 118 | * @return 119 | * @throws IOException 120 | */ 121 | public ChatResponse sendTemplate(String customerNumber, String channelNumber, String templateId, String header, List body) throws IOException { 122 | return sendMessage(customerNumber, channelNumber, new TemplateMessageBody(templateId, header, body)); 123 | } 124 | 125 | 126 | /** 127 | * Send a message 128 | *

129 | * Asynchronously send the request and notify {@code callback} of its response or if an error 130 | * occurred 131 | *

132 | * @param customerNumber 133 | * @param channelNumber 134 | * @param body 135 | * @param callback 136 | */ 137 | public void sendMessage(String customerNumber, String channelNumber, MessageBody body, Callback callback) { 138 | ChatMessage message = new ChatMessage(); 139 | message.body = body; 140 | message.username = mUsername; 141 | message.waNumber = channelNumber; 142 | message.phoneNumber = customerNumber; 143 | 144 | chat.sendMessage(message).enqueue(makeCallback(new Callback() { 145 | @Override 146 | public void onSuccess(ChatResponse data) { 147 | callback.onSuccess(data); 148 | } 149 | 150 | @Override 151 | public void onFailure(Throwable throwable) { 152 | callback.onFailure(throwable); 153 | } 154 | })); 155 | } 156 | 157 | 158 | /** 159 | * Send a message with text 160 | * @param customerNumber 161 | * @param channelNumber 162 | * @param text 163 | * @param callback 164 | */ 165 | public void sendMessage(String customerNumber, String channelNumber, String text, Callback callback) { 166 | sendMessage(customerNumber, channelNumber, new TextMessageBody(text), callback); 167 | } 168 | 169 | 170 | /** 171 | * Send a message with media 172 | * @param customerNumber 173 | * @param channelNumber 174 | * @param mediaType 175 | * @param url 176 | * @param callback 177 | */ 178 | public void sendMessage(String customerNumber, String channelNumber, MediaMessageBody.MediaType mediaType, String url, Callback callback) { 179 | sendMessage(customerNumber, channelNumber, new MediaMessageBody(mediaType, url), callback); 180 | } 181 | 182 | 183 | 184 | /** 185 | * Send a template message 186 | * @param customerNumber 187 | * @param channelNumber 188 | * @param templateId 189 | * @param header 190 | * @param body 191 | * @param callback 192 | */ 193 | public void sendTemplate(String customerNumber, String channelNumber, String templateId, String header, List body, Callback callback) { 194 | sendMessage(customerNumber, channelNumber, new TemplateMessageBody(templateId, header, body), callback); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /libs/voice/src/main/java/com/africastalking/VoiceService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.voice.CallResponse; 4 | import com.africastalking.voice.CallTransferResponse; 5 | import com.africastalking.voice.QueuedCallsResponse; 6 | 7 | import retrofit2.Call; 8 | import retrofit2.Response; 9 | import retrofit2.http.Field; 10 | import retrofit2.http.FormUrlEncoded; 11 | import retrofit2.http.POST; 12 | 13 | import java.io.IOException; 14 | 15 | public final class VoiceService extends Service { 16 | 17 | private VoiceService sInstance; 18 | private IVoice voice; 19 | 20 | private VoiceService(String username, String apiKey) { 21 | super(username, apiKey); 22 | } 23 | 24 | 25 | VoiceService() { 26 | super(); 27 | } 28 | 29 | @Override 30 | protected VoiceService getInstance(String username, String apiKey) { 31 | if (sInstance == null) { 32 | sInstance = new VoiceService(username, apiKey); 33 | } 34 | return sInstance; 35 | } 36 | 37 | @Override 38 | protected boolean isInitialized() { 39 | return sInstance != null; 40 | } 41 | 42 | @Override 43 | protected void initService() { 44 | String baseUrl = "https://voice."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN) + "/"; 45 | voice = mRetrofitBuilder 46 | .baseUrl(baseUrl) 47 | .build() 48 | .create(IVoice.class); 49 | } 50 | 51 | @Override 52 | protected void destroyService() { 53 | if (sInstance != null) { 54 | sInstance = null; 55 | } 56 | } 57 | 58 | /** 59 | * Initiate phone call 60 | * @param to Phone number to call 61 | * @param from Number from which to initiate the call 62 | * @return {@link com.africastalking.voice.CallResponse CallResponse} 63 | * @throws IOException 64 | */ 65 | public CallResponse call(String to, String from) throws IOException { 66 | checkPhoneNumber(to); 67 | Call call = voice.call(mUsername, to, from); 68 | Response resp = call.execute(); 69 | if (!resp.isSuccessful()) { 70 | throw new IOException(resp.errorBody().string()); 71 | } 72 | return resp.body(); 73 | } 74 | 75 | public CallResponse call(String to) throws IOException { 76 | return call(to, ""); 77 | } 78 | 79 | 80 | /** 81 | * Initiate phone call 82 | * @param to Phone number to call 83 | * @param from Number from which to initiate the call 84 | * @param callback {@link com.africastalking.Callback Callback} 85 | */ 86 | public void call(String to, String from, final Callback callback) { 87 | try { 88 | checkPhoneNumber(to); 89 | Call call = voice.call(mUsername, to, from); 90 | call.enqueue(makeCallback(callback)); 91 | } catch (IOException ex){ 92 | callback.onFailure(ex); 93 | } 94 | } 95 | 96 | public void call(String to, Callback callback) { 97 | call(to, "", callback); 98 | } 99 | 100 | 101 | /** 102 | * Initiate a call transfer 103 | * @param phoneNumber Phone Number to transfer the call to 104 | * @param sessionId Session Id of the ongoing call, it must have two legs 105 | * @return 106 | * @throws IOException 107 | */ 108 | public CallTransferResponse callTransfer(String phoneNumber, String sessionId) throws IOException { 109 | return callTransfer(phoneNumber, sessionId, "", ""); 110 | } 111 | public void callTransfer(String phoneNumber, String sessionId, Callback callback) { 112 | callTransfer(phoneNumber, sessionId,"", "", callback); 113 | } 114 | 115 | public CallTransferResponse callTransfer(String phoneNumber, String sessionId, String callLeg) throws IOException { 116 | return callTransfer(phoneNumber, sessionId, callLeg, ""); 117 | } 118 | public void callTransfer(String phoneNumber, String sessionId, String callLeg, Callback callback) { 119 | callTransfer(phoneNumber, sessionId, callLeg, "", callback); 120 | } 121 | 122 | public CallTransferResponse callTransfer(String phoneNumber, String sessionId, String callLeg, String holdMusicUrl) throws IOException { 123 | checkPhoneNumber(phoneNumber); 124 | Call call = voice.callTransfer(mUsername, phoneNumber, sessionId, callLeg.isEmpty() ? null : callLeg, holdMusicUrl.isEmpty() ? null : holdMusicUrl); 125 | Response resp = call.execute(); 126 | if (!resp.isSuccessful()) { 127 | throw new IOException(resp.errorBody().string()); 128 | } 129 | return resp.body(); 130 | } 131 | public void callTransfer(String phoneNumber, String sessionId, String callLeg, String holdMusicUrl, Callback callback) { 132 | try { 133 | checkPhoneNumber(phoneNumber); 134 | Call call = voice.callTransfer(mUsername, phoneNumber, sessionId, callLeg.isEmpty() ? null : callLeg, holdMusicUrl.isEmpty() ? null : holdMusicUrl); 135 | call.enqueue(makeCallback(callback)); 136 | } catch (IOException ex){ 137 | callback.onFailure(ex); 138 | } 139 | } 140 | 141 | 142 | /** 143 | * Fetch queued calls 144 | * @param phoneNumber Your virtual phone number 145 | * @return {@link com.africastalking.voice.QueuedCallsResponse QueuedCallsResponse} 146 | * @throws IOException 147 | */ 148 | public QueuedCallsResponse fetchQueuedCalls(String phoneNumber) throws IOException { 149 | Call call = voice.queueStatus(mUsername, phoneNumber); 150 | Response resp = call.execute(); 151 | if (!resp.isSuccessful()) { 152 | throw new IOException(resp.errorBody().string()); 153 | } 154 | return resp.body(); 155 | } 156 | 157 | public void fetchQueuedCalls(String phoneNumber, final Callback callback) { 158 | Call call = voice.queueStatus(mUsername, phoneNumber); 159 | call.enqueue(makeCallback(callback)); 160 | } 161 | 162 | /** 163 | * Upload media file 164 | * @param phoneNumber Your virtual phone number 165 | * @param url URL to your media file 166 | * @throws IOException 167 | */ 168 | public String uploadMediaFile(String phoneNumber, String url) throws IOException { 169 | Call call = voice.mediaUpload(mUsername, url, phoneNumber); 170 | Response resp = call.execute(); 171 | if (!resp.isSuccessful()) { 172 | throw new IOException(resp.errorBody().string()); 173 | } 174 | return resp.body(); 175 | } 176 | 177 | public void uploadMediaFile(String phoneNumber, String url, final Callback callback) { 178 | Call call = voice.mediaUpload(mUsername, url, phoneNumber); 179 | call.enqueue(makeCallback(callback)); 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /example/src/main/java/com/africastalking/example/App.java: -------------------------------------------------------------------------------- 1 | package com.africastalking.example; 2 | 3 | import com.africastalking.*; 4 | import com.africastalking.voice.action.*; 5 | import com.africastalking.voice.action.Record; 6 | import com.google.gson.Gson; 7 | 8 | import java.io.IOException; 9 | import java.net.*; 10 | import java.util.Arrays; 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | import spark.ModelAndView; 15 | import spark.template.handlebars.HandlebarsTemplateEngine; 16 | 17 | import static spark.Spark.exception; 18 | import static spark.Spark.get; 19 | import static spark.Spark.port; 20 | import static spark.Spark.post; 21 | import static spark.Spark.staticFiles; 22 | 23 | public class App { 24 | 25 | private static final int HTTP_PORT = 8080; 26 | private static final String USERNAME = "fake"; 27 | private static final String API_KEY = "fake"; 28 | 29 | private static Gson gson = new Gson(); 30 | 31 | private static HandlebarsTemplateEngine hbs = new HandlebarsTemplateEngine("/views"); 32 | private static SmsService sms; 33 | private static AirtimeService airtime; 34 | 35 | private static void log(String message) { 36 | System.out.println(message); 37 | } 38 | 39 | private static void setupAfricastalking() throws IOException { 40 | AfricasTalking.initialize(USERNAME, API_KEY); 41 | AfricasTalking.setLogger(new Logger(){ 42 | @Override 43 | public void log(String message, Object... args) { 44 | System.out.println(message); 45 | } 46 | }); 47 | sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 48 | airtime = AfricasTalking.getService(AirtimeService.class); 49 | } 50 | 51 | public static void main(String[] args) throws IOException { 52 | 53 | InetAddress host = Inet4Address.getLocalHost(); 54 | log("\n"); 55 | log(String.format("HTTP Server: %s:%d", host.getHostAddress(), HTTP_PORT)); 56 | log("\n"); 57 | 58 | HashMap states = new HashMap<>(); 59 | String baseUrl = "http://fake.ngrok.io"; 60 | String songUrl = "https://upload.wikimedia.org/wikipedia/commons/transcoded/4/49/National_Anthem_of_Kenya.ogg/National_Anthem_of_Kenya.ogg.mp3"; 61 | 62 | setupAfricastalking(); 63 | 64 | port(HTTP_PORT); 65 | 66 | staticFiles.location("/public"); 67 | exception(Exception.class, (e, req, res) -> e.printStackTrace()); // print all exceptions 68 | 69 | get("/", (req, res) -> { 70 | Map data = new HashMap<>(); 71 | data.put("req", req.pathInfo()); 72 | return render("index", data); 73 | }); 74 | 75 | // Send SMS 76 | post("/auth/register/:phone", (req, res) -> sms.send("Welcome to Awesome Company", "AT2FA", new String[] {req.params("phone")}, false), gson::toJson); 77 | 78 | // Send Airtime 79 | post("/airtime/:phone", (req, res) -> airtime.send(req.params("phone"), req.queryParams("currencyCode"), Float.parseFloat(req.queryParams("amount"))), gson::toJson); 80 | 81 | post("/voice", (req, res) -> { 82 | 83 | // Parse POST data 84 | String[] raw = URLDecoder.decode(req.body()).split("&"); 85 | Map data = new HashMap<>(); 86 | for (String item: raw) { 87 | String [] kw = item.split("="); 88 | if (kw.length == 2) { 89 | data.put(kw[0], kw[1]); 90 | } 91 | } 92 | 93 | // Prep state 94 | boolean isActive = data.get("isActive").contentEquals("1"); 95 | String sessionId = data.get("sessionId"); 96 | String callerNumber = data.get("callerNumber"); 97 | String dtmf = data.get("dtmfDigits"); 98 | String state = isActive ? states.getOrDefault(sessionId, "menu") : ""; 99 | 100 | ActionBuilder response = new ActionBuilder(); 101 | 102 | switch (state) { 103 | case "menu": 104 | states.put(sessionId, "process"); 105 | response 106 | .say(new Say("Hello bot " + data.getOrDefault("callerNumber", "There"))) 107 | .getDigits(new GetDigits(new Say("Press 1 to listen to some song. Press 2 to tell me your name. Press 3 to talk to a human. Press 4 or hang up to quit"), 1, "#", null)); 108 | break; 109 | case "process": 110 | switch (dtmf) { 111 | case "1": 112 | states.put(sessionId, "menu"); 113 | response 114 | .play(new Play(new URL(songUrl))) 115 | .redirect(new Redirect(new URL(baseUrl + "/voice"))); 116 | break; 117 | case "2": 118 | states.put(sessionId, "name"); 119 | response.record(new Record(new Say("Please say your full name after the beep"), "#", 30, 0, true, true, null)); 120 | break; 121 | case "3": 122 | states.remove(sessionId); 123 | response 124 | .say(new Say("We are getting our resident human on the line for you, please wait while enjoying this nice tune. You have 30 seconds to enjoy a nice conversation with them")) 125 | .dial(new Dial(Arrays.asList("+254718769882"), false, false, null, new URL(songUrl), 30)); 126 | break; 127 | case "4": 128 | states.remove(sessionId); 129 | response.say(new Say("Bye Bye, Long Live Our Machine Overlords")).reject(new Reject()); 130 | break; 131 | default: 132 | states.put(sessionId, "menu"); 133 | response 134 | .say(new Say("Invalid choice, try again and you will be exterminated!")) 135 | .redirect(new Redirect(new URL(baseUrl + "/voice"))); 136 | break; 137 | 138 | } 139 | break; 140 | case "name": 141 | states.put(sessionId, "menu"); 142 | response 143 | .say(new Say("Your human name is")) 144 | .play(new Play(new URL(data.get("recordingUrl")))) 145 | .say(new Say("Now forget is, you new name is bot " + callerNumber)) 146 | .redirect(new Redirect(new URL(baseUrl + "/voice"))); 147 | break; 148 | default: 149 | response.say(new Say("Well, this is unexpected! Bye Bye, Long Live Our Machine Overlords")).reject(new Reject()); 150 | break; 151 | } 152 | 153 | return response.build(); 154 | }); 155 | 156 | } 157 | 158 | private static String render(String view, Map data) { 159 | return hbs.render(new ModelAndView(data, view + ".hbs")); 160 | } 161 | 162 | } 163 | -------------------------------------------------------------------------------- /libs/mobileData/src/main/java/com/africastalking/MobileDataService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | import com.africastalking.mobileData.recipient.MobileDataRecipient; 4 | import com.africastalking.mobileData.response.*; 5 | import com.africastalking.mobileData.Transaction; 6 | import retrofit2.Call; 7 | import retrofit2.Response; 8 | 9 | import java.io.IOException; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | 13 | public final class MobileDataService extends Service { 14 | 15 | private MobileDataService sInstance; 16 | private IMobileData mobileData; 17 | 18 | 19 | private MobileDataService(String username, String apiKey) { 20 | super(username, apiKey); 21 | } 22 | 23 | MobileDataService() { 24 | super(); 25 | } 26 | 27 | @Override 28 | protected MobileDataService getInstance(String username, String apiKey) { 29 | if(sInstance == null) { 30 | sInstance = new MobileDataService(username, apiKey); 31 | } 32 | return sInstance; 33 | } 34 | 35 | @Override 36 | protected boolean isInitialized() { 37 | return sInstance != null; 38 | } 39 | 40 | @Override 41 | protected void initService() { 42 | String baseUrl = "https://bundles."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN) + "/"; 43 | mobileData = mRetrofitBuilder 44 | .baseUrl(baseUrl) 45 | .build() 46 | .create(IMobileData.class); 47 | } 48 | 49 | @Override 50 | protected void destroyService() { 51 | if (sInstance != null) { 52 | sInstance = null; 53 | } 54 | } 55 | 56 | public void setIdempotencyKey(String key) { 57 | this.mIdempotencyKey = key; 58 | } 59 | 60 | 61 | private HashMap makeMobileDataRequest(String product, List recipients) { 62 | HashMap body = new HashMap<>(); 63 | body.put("username", mUsername); 64 | body.put("productName", product); 65 | body.put("recipients", recipients); 66 | return body; 67 | } 68 | 69 | /** 70 | * Send mobile data from product to recipients 71 | * @param product 72 | * @param recipients 73 | * @return 74 | * @throws IOException 75 | */ 76 | public MobileDataResponse send(String product, List recipients) throws IOException { 77 | for(MobileDataRecipient recipient : recipients) { checkPhoneNumber(recipient.phoneNumber); } 78 | 79 | HashMap body = makeMobileDataRequest(product, recipients); 80 | Call call = mobileData.requestMobileData(body); 81 | Response resp = call.execute(); 82 | if (!resp.isSuccessful()) { 83 | throw new IOException(resp.errorBody().string()); 84 | } 85 | return resp.body(); 86 | } 87 | 88 | /** 89 | * Send mobile data from product to recipients 90 | * @param product 91 | * @param recipients 92 | * @param callback 93 | */ 94 | public void send(String product, List recipients, Callback callback) { 95 | try { 96 | for(MobileDataRecipient recipient : recipients) { checkPhoneNumber(recipient.phoneNumber); } 97 | 98 | HashMap body = makeMobileDataRequest(product, recipients); 99 | Call call = mobileData.requestMobileData(body); 100 | call.enqueue(makeCallback(callback)); 101 | 102 | } catch (IOException ex) { 103 | callback.onFailure(ex); 104 | } 105 | } 106 | 107 | /** 108 | * Find a transaction by id 109 | * @param transactionId 110 | * @return 111 | * @throws IOException 112 | */ 113 | public Transaction findTransaction(String transactionId) throws IOException { 114 | HashMap query = new HashMap(); 115 | query.put("username", mUsername); 116 | query.put("transactionId", transactionId); 117 | Call call = mobileData.findTransaction(query); 118 | Response resp = call.execute(); 119 | if (!resp.isSuccessful()) { 120 | throw new IOException(resp.errorBody().string()); 121 | } 122 | 123 | FindTransactionResponse body = resp.body(); 124 | if (body.status.contentEquals("Failure")) { 125 | throw new IOException(body.errorMessage); 126 | } 127 | 128 | if (body.status.contentEquals("Throttled")) { 129 | throw new IOException("Too many requests: Throttled"); 130 | } 131 | 132 | return resp.body().data; 133 | } 134 | 135 | /** 136 | * Find a transaction by id 137 | * @param transactionId 138 | * @param callback 139 | */ 140 | public void findTransaction(String transactionId, Callback callback) { 141 | HashMap query = new HashMap(); 142 | query.put("username", mUsername); 143 | query.put("transactionId", transactionId); 144 | Call call = mobileData.findTransaction(query); 145 | call.enqueue(new retrofit2.Callback() { 146 | @Override 147 | public void onResponse(Call call, Response response) { 148 | if (response.isSuccessful()) { 149 | 150 | FindTransactionResponse body = response.body(); 151 | if (body.status.contentEquals("Failure")) { 152 | callback.onFailure(new IOException(body.errorMessage)); 153 | } 154 | 155 | if (body.status.contentEquals("Throttled")) { 156 | callback.onFailure(new IOException("Too many requests: Throttled")); 157 | } 158 | 159 | callback.onSuccess(body.data); 160 | } else { 161 | callback.onFailure(new Exception(response.message())); 162 | } 163 | } 164 | 165 | @Override 166 | public void onFailure(Call call, Throwable t) { 167 | callback.onFailure(t); 168 | } 169 | }); 170 | } 171 | 172 | /** 173 | * Fetch wallet balance 174 | * @return 175 | * @throws IOException 176 | */ 177 | public WalletBalanceResponse fetchWalletBalance() throws IOException { 178 | HashMap filters = new HashMap<>(); 179 | filters.put("username", mUsername); 180 | 181 | Call call = mobileData.fetchWalletBalance(filters); 182 | Response resp = call.execute(); 183 | if (!resp.isSuccessful()) { 184 | throw new IOException(resp.errorBody().string()); 185 | } 186 | 187 | WalletBalanceResponse body = resp.body(); 188 | if (body.status.contentEquals("Failure")) { 189 | throw new IOException(body.errorMessage); 190 | } 191 | 192 | if (body.status.contentEquals("Throttled")) { 193 | throw new IOException("Too many requests: Throttled"); 194 | } 195 | 196 | return body; 197 | } 198 | 199 | /** 200 | * Fetch wallet balance 201 | * @param callback 202 | */ 203 | public void fetchWalletBalance(Callback callback) { 204 | HashMap filters = new HashMap<>(); 205 | filters.put("username", mUsername); 206 | 207 | Call call = mobileData.fetchWalletBalance(filters); 208 | 209 | call.enqueue(new retrofit2.Callback() { 210 | @Override 211 | public void onResponse(Call call, Response response) { 212 | if (response.isSuccessful()) { 213 | WalletBalanceResponse body = response.body(); 214 | 215 | if (body.status.contentEquals("Failure")) { 216 | callback.onFailure(new IOException(body.errorMessage)); 217 | } 218 | 219 | if (body.status.contentEquals("Throttled")) { 220 | callback.onFailure(new IOException("Too many requests: Throttled")); 221 | } 222 | 223 | callback.onSuccess(body); 224 | } else { 225 | callback.onFailure(new Exception(response.message())); 226 | } 227 | } 228 | 229 | @Override 230 | public void onFailure(Call call, Throwable t) { 231 | callback.onFailure(t); 232 | } 233 | }); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Africa's Talking 2 | 3 | [![Release](https://jitpack.io/v/AfricasTalkingLtd/africastalking-java.svg)](https://jitpack.io/#AfricasTalkingLtd/africastalking-java) 4 | 5 | > 6 | > This SDK provides convenient access to the Africa's Talking API from apps written in Java/Kotlin/Scala with support for JDK8+. 7 | > 8 | 9 | 10 | ## Documentation 11 | Take a look at the [API docs here](https://developers.africastalking.com/). 12 | 13 | ## Install 14 | 15 | You can depend on the jars through Maven (from `https://jitpack.io`): 16 | ```xml 17 | 18 | 19 | jitpack.io 20 | https://jitpack.io 21 | 22 | 23 | ... 24 | 25 | com.github.AfricasTalkingLtd.africastalking-java 26 | core 27 | v3.5.3 28 | 29 | ``` 30 | or sbt: 31 | 32 | ``` 33 | resolvers += "jitpack" at "https://jitpack.io" 34 | // Get all services 35 | libraryDependencies += "com.github.AfricasTalkingLtd.africastalking-java" % "core" % "v3.5.3" 36 | ``` 37 | 38 | or Gradle (Groovy DSL): 39 | ```groovy 40 | repositories { 41 | maven { 42 | url "https://jitpack.io" 43 | } 44 | } 45 | 46 | dependencies{ 47 | // Get all services 48 | implementation 'com.github.AfricasTalkingLtd.africastalking-java:core:v3.5.3' 49 | ``` 50 | 51 | or Gradle (Kotlin DSL): 52 | ```kotlin 53 | repositories { 54 | jcenter() 55 | maven { setUrl("https://jitpack.io") } 56 | } 57 | 58 | dependencies{ 59 | // Get all services 60 | implementation("com.github.AfricasTalkingLtd.africastalking-java:core:v3.5.3") 61 | } 62 | ``` 63 | 64 | ## Usage 65 | 66 | The SDK needs to be initialized with your app username and API key, which you get from the [dashboard](https://account.africastalking.com). 67 | 68 | > You can use this SDK for either production or sandbox apps. For sandbox, the app username is **ALWAYS** `sandbox` 69 | 70 | ```java 71 | // Initialize 72 | String username = "YOUR_USERNAME"; // use 'sandbox' for development in the test environment 73 | String apiKey = "YOUR_API_KEY"; // use your sandbox app API key for development in the test environment 74 | AfricasTalking.initialize(username, apiKey); 75 | 76 | // Initialize a service e.g. SMS 77 | SmsService sms = AfricasTalking.getService(AfricasTalking.SERVICE_SMS); 78 | 79 | // Use the service 80 | List response = sms.send("Hello Message!", new String[] {"+2547xxxxxx"}, true); 81 | ``` 82 | 83 | See [example](example/) for more usage examples. 84 | 85 | 86 | ## Initialization 87 | 88 | The following static methods are available on the `AfricasTalking` class to initialize the library: 89 | 90 | - `initialize(string username, String apiKey)`: Initialize the library. 91 | 92 | - `setLogger(Logger)`: Set logging object. 93 | 94 | - `getService(Service.class | AfricasTalking.SERVICE_*)`: Get an instance to a given service by name or by class: 95 | 96 | - [SMS Service](#smsservice): `AfricasTalking.getService(AfricasTalking.SERVICE_SMS)` 97 | - [Chat Service](#chatservice): `AfricasTalking.getService(AfricasTalking.SERVICE_CHAT)` 98 | - [Airtime Service](#airtimeservice): `AfricasTalking.getService(AfricasTalking.SERVICE_AIRTIME)` 99 | - [Voice Service](#voiceservice): `AfricasTalking.getService(AfricasTalking.SERVICE_VOICE)` 100 | - [Insights Service](#insightsservice): `AfricasTalking.getService(AfricasTalking.SERVICE_INSIGHTS)` 101 | - [Mobile Data Service](#mobiledataservice): `AfricasTalking.getService(AfricasTalking.SERVICE_MOBILE_DATA)` 102 | - [Application Service](#applicationservice): `AfricasTalking.getService(AfricasTalking.SERVICE_APPLICATION)` 103 | 104 | > Note on **USSD**: For more information, please read [this](https://developers.africastalking.com/docs/ussd/overview) 105 | 106 | ## Services 107 | 108 | All methods are synchronous (i.e. will block current thread) but provide asynchronous variants that take a `com.africastalking.Callback` as the last argument. 109 | 110 | All phone numbers use the international format. e.g. `+234xxxxxxxx`. 111 | 112 | ### `ApplicationService` 113 | 114 | - `fetchApplicationData()`: Get app information. e.g. balance 115 | 116 | ### `AirtimeService` 117 | 118 | - `setIdempotencyKey(String key)`: Set the [**Idempotency-Key**](https://developers.africastalking.com/docs/idempotent_requests) for the next request. Call this function every time before calling `send()` if you're sending the same amount to the same phone number. 119 | 120 | - `setMaxRetry(int retries)`: Set the maximum number of retries in case of failed airtime deliveries due to telco unavailability or any other reason. 121 | 122 | - `send(String phoneNumber, String currencyCode, float amount)`: Send airtime to a phone number. Example amount would be `KES 150`. 123 | 124 | - `send(HashMap recipients)`: Send airtime to many of phone numbers. The keys in the `recipients` map are phone numbers while the values are airtime amounts. The amounts need to have currency info e.g. `UXG 4265`. 125 | 126 | For more information about status notification, please read the airtime [documentation](https://developers.africastalking.com/docs/airtime/notifications/validation) 127 | 128 | ### `SmsService` 129 | 130 | - `send(String message, String from, String[] recipients, boolean enqueue)`: Send a message. 131 | 132 | - `message`: SMS content 133 | - `from`: Short code or alphanumeric ID that is registered with Africa's Talking account. 134 | - `recipients`: An array of phone numbers. 135 | - `enqueue`: Set to true if you would like to deliver as many messages to the API without waiting for an acknowledgement from telcos. 136 | 137 | 138 | 139 | - `sendPremium(String message, String keyword, String linkId, long retryDurationInHours, String[] recipients)`: Send a premium SMS 140 | 141 | - `message`: SMS content 142 | - `keyword`: You premium product keyword 143 | - `linkId`: "[...] We forward the `linkId` to your application when the user send a message to your service" 144 | - `retryDurationInHours`: "It specifies the number of hours your subscription message should be retried in case it's not delivered to the subscriber" 145 | - `recipients`: An array of phon numbers. 146 | 147 | 148 | 149 | - `fetchMessages(long lastReceivedId)`: Fetch your messages 150 | 151 | - `lastReceivedId`: "This is the id of the message that you last processed". Defaults to `0` 152 | 153 | 154 | 155 | - `fetchSubscriptions(String shortCode, String keyword, long lastReceivedId)`: Fetch your premium subscription data. 156 | 157 | - `shortCode`: This is the premium short code mapped to your account. 158 | - `keyword`: A premium keyword under the above short code and mapped to your account. 159 | - `lastReceivedId`: "This is the id of the message that you last processed". Defaults to `0` 160 | 161 | 162 | 163 | - `createSubscription(String shortCode, String keyword, String phoneNumber)`: Create a premium subscription. 164 | 165 | - `shortCode`: This is the premium short code mapped to your account. 166 | - `keyword`: A premium keyword under the above short code and mapped to your account. 167 | - `phoneNumber`: The phone number to be subscribed 168 | 169 | 170 | 171 | - `deleteSubscription(String shortCode, String keyword, String phoneNumber)`: Remove a phone number from a premium subscription. 172 | 173 | - `shortCode`: This is the premium short code mapped to your account. 174 | - `keyword`: A premium keyword under the above short code and mapped to your account. 175 | - `phoneNumber`: The phone number to be unsubscribed 176 | 177 | 178 | 179 | For more information, read the following: 180 | 181 | - [How to receive SMS](https://developers.africastalking.com/docs/sms/notifications) 182 | - [How to get notified of delivery reports](https://developers.africastalking.com/docs/sms/notifications) 183 | - [How to listen for subscription notifications](https://developers.africastalking.com/docs/sms/notifications) 184 | 185 | ### `ChatService` 186 | 187 | - `sendMessage(String customerNumber, String channelNumber, String text)`: Send a message with text. 188 | 189 | - `customerNumber`: The destination, which is the phone number of the customer. 190 | - `channelNumber`: The channel number that will be used to send out the message. Examples are a WhatsApp phone number or a Telegram username. 191 | - `text`: The text message to send 192 | 193 | - `sendMessage(String customerNumber, String channelNumber, MediaType type, String url)`: Send a message with media. 194 | 195 | - `customerNumber`: The destination, which is the phone number of the customer 196 | - `channelNumber`: The channel number that will be used to send out the message. Examples are a WhatsApp phone number or a Telegram username. 197 | - `type`: The media type. Possible values are Image, Audio, Video, Document, Voice, Sticker. 198 | - `url`: A valid URL location from where the media can be downloaded from. 199 | 200 | - `sendTemplate(String customerNumber, String channelNumber, String templateId, String header, List body)`: Send a template message. 201 | 202 | - `customerNumber`: The destination, which is the phone number of the customer 203 | - `channelNumber`: The channel number that will be used to send out the message. Examples are a WhatsApp phone number or a Telegram username. 204 | - `templateId`: Specifies a registered template message to send. 205 | - `header`: Header replace value 206 | - `body`: List of body replacement values 207 | 208 | 209 | ### `VoiceService` 210 | 211 | - `call(String phoneNumber)`: Initiate a phone call 212 | 213 | - `phoneNumber`: Phone number to call 214 | 215 | 216 | 217 | - `fetchQueuedCalls(String phoneNumber)`: Get queued calls on a phone number. 218 | 219 | - `phoneNumber`: Your Africa's Talking issued virtual phone number 220 | 221 | 222 | 223 | - `uploadMediaFile(String phoneNumber, String url)`: Upload voice media file 224 | 225 | - `phoneNumber`: Your Africa's Talking issued virtual phone number 226 | - `url`: URL to your media file. 227 | 228 | 229 | 230 | - `ActionBuilder`: Build voice xml when callback URL receives a `POST` from Africa's Talking 231 | 232 | - `say()`: Add a `Say` action. 233 | 234 | - `play()`: Add a `Play` action. 235 | 236 | - `getDigits()`: Add a `GetDigits` action. 237 | 238 | - `dial()`: Add a `Dial` action. 239 | 240 | - `conference()`: Add a `Conferemce` action. 241 | 242 | - `record()`: Add a `Record` action. 243 | 244 | - `enqueue()`: Add a `Enqueue` action. 245 | 246 | - `dequeue()`: Add a `Dequeue` action. 247 | 248 | - `reject()`: Add a `Reject` action. 249 | 250 | - `redirect()`: Add a `Redirect` action. 251 | 252 | - `build()`: Finally build the xml 253 | 254 | 255 | For more information on voice, please read the [documentation](https://developers.africastalking.com/docs/voice/overview) 256 | 257 | ### `InsightsService` 258 | 259 | - `checkSimSwapState(phoneNumbers: List)`: Check the sim swap state of phone number(s). 260 | 261 | For more information on insights, please read the [documentation](https://developers.africastalking.com/docs/insights/overview?preview=true) 262 | 263 | ### `MobileDataService` 264 | 265 | - `send(String product, List recipients)`: Send mobile data from a given product. 266 | 267 | - `findTransaction(String transactionId)`: Find a mobile data transaction 268 | 269 | - `fetchWalletBalance()`: Fetch a mobile data product balance 270 | 271 | 272 | For more information on mobile data, please read the [documentation](https://developers.africastalking.com/docs/data/overview) 273 | 274 | 275 | ## Development 276 | ```shell 277 | $ git clone https://github.com/AfricasTalkingLtd/africastalking-java.git 278 | $ cd africastalking-java 279 | $ touch local.properties 280 | ``` 281 | 282 | Make sure your `local.properties` file has the following content then run `./gradlew build` 283 | 284 | ```ini 285 | # AT API 286 | api.username=sandbox 287 | api.key=some_key 288 | ``` 289 | 290 | ## Issues 291 | 292 | If you find a bug, please file an issue on [our issue tracker on GitHub](https://github.com/AfricasTalkingLtd/africastalking-java/issues). 293 | 294 | -------------------------------------------------------------------------------- /libs/sms/src/main/java/com/africastalking/SmsService.java: -------------------------------------------------------------------------------- 1 | package com.africastalking; 2 | 3 | 4 | import com.africastalking.sms.FetchMessageResponse; 5 | import com.africastalking.sms.FetchSubscriptionResponse; 6 | import com.africastalking.sms.Message; 7 | import com.africastalking.sms.Recipient; 8 | import com.africastalking.sms.SendMessageResponse; 9 | import com.africastalking.sms.Subscription; 10 | import com.africastalking.sms.SubscriptionResponse; 11 | 12 | import retrofit2.Response; 13 | 14 | import java.io.IOException; 15 | import java.util.List; 16 | import java.util.StringJoiner; 17 | 18 | 19 | /** 20 | * SMS Service; Send and fetch SMSs 21 | */ 22 | public final class SmsService extends Service { 23 | 24 | 25 | private static SmsService sInstance; 26 | private IBulkSMS bulkSMS; 27 | private IPremiumSMS premiumSMS; 28 | 29 | private SmsService(String username, String apiKey) { 30 | super(username, apiKey); 31 | } 32 | 33 | SmsService() { 34 | super(); 35 | } 36 | 37 | @Override 38 | protected SmsService getInstance(String username, String apiKey) { 39 | 40 | if (sInstance == null) { 41 | sInstance = new SmsService(username, apiKey); 42 | } 43 | 44 | return sInstance; 45 | } 46 | 47 | @Override 48 | protected void initService() { 49 | String bulkSmsBaseUrl = "https://api."+ (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN) + "/version1/"; 50 | bulkSMS = mRetrofitBuilder.baseUrl(bulkSmsBaseUrl).build().create(IBulkSMS.class); 51 | 52 | String premiumSmsBaseUrl = "https://" + (isSandbox ? "api." : "content.") + (isSandbox ? Const.SANDBOX_DOMAIN : Const.PRODUCTION_DOMAIN) + "/version1/"; 53 | premiumSMS = mRetrofitBuilder.baseUrl(premiumSmsBaseUrl).build().create(IPremiumSMS.class); 54 | } 55 | 56 | @Override 57 | protected boolean isInitialized() { 58 | return sInstance != null; 59 | } 60 | 61 | @Override 62 | protected void destroyService() { 63 | if (sInstance != null) { 64 | sInstance = null; 65 | } 66 | } 67 | 68 | private String formatRecipients(String[] recipients) throws IOException { 69 | 70 | if (recipients == null){ 71 | return null; 72 | } 73 | 74 | if (recipients.length == 1) { 75 | checkPhoneNumber(recipients[0]); 76 | return recipients[0]; 77 | } 78 | 79 | StringJoiner joiner = new StringJoiner(","); 80 | for (CharSequence cs: recipients) { 81 | checkPhoneNumber(cs.toString()); 82 | joiner.add(cs); 83 | } 84 | return joiner.toString(); 85 | } 86 | 87 | // -> Bulk 88 | 89 | /** 90 | * Send a message 91 | *

92 | * Synchronously send the request and return its response. 93 | *

94 | * @param message 95 | * @param from 96 | * @param recipients 97 | * @param enqueue 98 | * @return 99 | * @throws IOException 100 | */ 101 | public List send(String message, String from, String[] recipients, boolean enqueue) throws IOException { 102 | Response resp = bulkSMS.send(mUsername, formatRecipients(recipients), from, message, 1, enqueue ? "1" : null).execute(); 103 | if (!resp.isSuccessful()) { 104 | throw new IOException(resp.errorBody().string()); 105 | } 106 | return resp.body().data.recipients; 107 | } 108 | 109 | /** 110 | * Send a message 111 | *

112 | * Asynchronously send the request and notify {@code callback} of its response or if an error 113 | * occurred 114 | *

115 | * @param message 116 | * @param from 117 | * @param recipients 118 | * @param enqueue 119 | * @param callback 120 | */ 121 | public void send(String message, String from, String[] recipients, boolean enqueue, final Callback> callback) { 122 | try { 123 | bulkSMS.send(mUsername, formatRecipients(recipients), from, message, 1, enqueue ? "1" : null).enqueue(makeCallback(new Callback() { 124 | @Override 125 | public void onSuccess(SendMessageResponse data) { 126 | callback.onSuccess(data.data.recipients); 127 | } 128 | 129 | @Override 130 | public void onFailure(Throwable throwable) { 131 | callback.onFailure(throwable); 132 | } 133 | })); 134 | } catch (IOException ex) { 135 | callback.onFailure(ex); 136 | } 137 | } 138 | 139 | /** 140 | * Send a message 141 | *

142 | * Synchronously send the request and return its response. 143 | *

144 | * @param message 145 | * @param recipients 146 | * @return 147 | * @throws IOException 148 | */ 149 | public List send(String message, String[] recipients, boolean enqueue) throws IOException { 150 | return send(message, null, recipients, enqueue); 151 | } 152 | 153 | 154 | /** 155 | * Send a message 156 | *

157 | * Asynchronously send the request and notify {@code callback} of its response or if an error 158 | * occurred 159 | *

160 | * @param message 161 | * @param recipients 162 | * @param callback 163 | */ 164 | public void send(String message, String[] recipients, boolean enqueue, Callback> callback) { 165 | send(message, null, recipients, enqueue, callback); 166 | } 167 | 168 | 169 | // -> Premium 170 | 171 | /** 172 | * Send premium SMS 173 | *

174 | * Synchronously send the request and return its response. 175 | *

176 | * @param message 177 | * @param from 178 | * @param keyword 179 | * @param linkId 180 | * @param retryDurationInHours 181 | * @param recipients 182 | * @return 183 | * @throws IOException 184 | */ 185 | public List sendPremium(String message, String from, String keyword, String linkId, long retryDurationInHours, String[] recipients) throws IOException { 186 | String retryDuration = retryDurationInHours <= 0 ? null : String.valueOf(retryDurationInHours); 187 | Response resp = premiumSMS.send(mUsername, formatRecipients(recipients), from, message, keyword, linkId, retryDuration, 0).execute(); 188 | if (!resp.isSuccessful()) { 189 | throw new IOException(resp.errorBody().string()); 190 | } 191 | return resp.body().data.recipients; 192 | } 193 | 194 | /** 195 | * Send premium SMS 196 | *

197 | * Asynchronously send the request and notify {@code callback} of its response or if an error 198 | * occurred 199 | *

200 | * @param message 201 | * @param from 202 | * @param keyword 203 | * @param linkId 204 | * @param retryDurationInHours 205 | * @param recipients 206 | * @param callback 207 | */ 208 | public void sendPremium(String message, String from, String keyword, String linkId, long retryDurationInHours, String[] recipients, final Callback> callback) { 209 | try { 210 | String retryDuration = retryDurationInHours <= 0 ? null : String.valueOf(retryDurationInHours); 211 | premiumSMS.send(mUsername, formatRecipients(recipients), 212 | from, message, keyword, linkId, retryDuration, 0) 213 | .enqueue(makeCallback(new Callback() { 214 | @Override 215 | public void onSuccess(SendMessageResponse data) { 216 | callback.onSuccess(data.data.recipients); 217 | } 218 | 219 | @Override 220 | public void onFailure(Throwable throwable) { 221 | callback.onFailure(throwable); 222 | } 223 | })); 224 | } catch (IOException ex) { 225 | callback.onFailure(ex); 226 | } 227 | } 228 | 229 | /** 230 | * Send premium SMS 231 | *

232 | * Synchronously send the request and return its response. 233 | *

234 | * @param message 235 | * @param keyword 236 | * @param linkId 237 | * @param retryDurationInHours 238 | * @param recipients 239 | * @return 240 | * @throws IOException 241 | */ 242 | public List sendPremium(String message, String keyword, String linkId, long retryDurationInHours, String[] recipients) throws IOException { 243 | return sendPremium(message, null, keyword, linkId, retryDurationInHours, recipients); 244 | } 245 | 246 | /** 247 | * Send premium SMS 248 | *

249 | * Asynchronously send the request and notify {@code callback} of its response or if an error 250 | * occurred 251 | *

252 | * @param message 253 | * @param keyword 254 | * @param linkId 255 | * @param retryDurationInHours 256 | * @param recipients 257 | * @param callback 258 | */ 259 | public void sendPremium(String message, String keyword, String linkId, long retryDurationInHours, String[] recipients, Callback> callback){ 260 | sendPremium(message, null, keyword, linkId, retryDurationInHours, recipients, callback); 261 | } 262 | 263 | /** 264 | * Send Premium SMS 265 | *

266 | * Synchronously send the request and return its response. 267 | *

268 | * @param message 269 | * @param from 270 | * @param keyword 271 | * @param linkId 272 | * @param recipients 273 | * @return 274 | * @throws IOException 275 | */ 276 | public List sendPremium(String message, String from, String keyword, String linkId, String[] recipients) throws IOException { 277 | return sendPremium(message, from, keyword, linkId, -1, recipients); 278 | } 279 | 280 | /** 281 | * Send premium SMS 282 | *

283 | * Asynchronously send the request and notify {@code callback} of its response or if an error 284 | * occurred 285 | *

286 | * @param message 287 | * @param from 288 | * @param keyword 289 | * @param linkId 290 | * @param recipients 291 | * @param callback 292 | */ 293 | public void sendPremium(String message, String from, String keyword, String linkId, String[] recipients, Callback> callback){ 294 | sendPremium(message, from, keyword, linkId, -1, recipients, callback); 295 | } 296 | 297 | /** 298 | * Send premium SMS 299 | *

300 | * Synchronously send the request and return its response. 301 | *

302 | * @param message 303 | * @param keyword 304 | * @param linkId 305 | * @param recipients 306 | * @return 307 | * @throws IOException 308 | */ 309 | public List sendPremium(String message, String keyword, String linkId, String[] recipients) throws IOException { 310 | return sendPremium(message, null, keyword, linkId, -1, recipients); 311 | } 312 | 313 | /** 314 | * Send premium SMS 315 | *

316 | * Asynchronously send the request and notify {@code callback} of its response or if an error 317 | * occurred 318 | *

319 | * @param message 320 | * @param keyword 321 | * @param linkId 322 | * @param recipients 323 | * @param callback 324 | */ 325 | public void sendPremium(String message, String keyword, String linkId, String[] recipients, Callback> callback){ 326 | sendPremium(message, null, keyword, linkId, -1, recipients, callback); 327 | } 328 | 329 | // -> Fetch Message 330 | 331 | /** 332 | * Fetch messages 333 | *

334 | * Synchronously send the request and return its response. 335 | *

336 | * @param lastReceivedId 337 | * @return 338 | * @throws IOException 339 | */ 340 | public List fetchMessages(long lastReceivedId) throws IOException { 341 | Response resp = bulkSMS.fetchMessages(mUsername, lastReceivedId).execute(); 342 | if (!resp.isSuccessful()) { 343 | throw new IOException(resp.errorBody().string()); 344 | } 345 | return resp.body().data.messages; 346 | } 347 | 348 | /** 349 | * Fetch messages 350 | *

351 | * Synchronously send the request and return its response. 352 | *

353 | * @return 354 | * @throws IOException 355 | */ 356 | public List fetchMessages() throws IOException { 357 | return fetchMessages(0); 358 | } 359 | 360 | /** 361 | * Fetch messages 362 | *

363 | * Asynchronously send the request and notify {@code callback} of its response or if an error 364 | * occurred 365 | *

366 | * @param lastReceivedId 367 | * @param callback 368 | */ 369 | public void fetchMessages(long lastReceivedId, final Callback> callback) { 370 | bulkSMS.fetchMessages(mUsername, lastReceivedId).enqueue(makeCallback(new Callback() { 371 | @Override 372 | public void onSuccess(FetchMessageResponse data) { 373 | callback.onSuccess(data.data.messages); 374 | } 375 | 376 | @Override 377 | public void onFailure(Throwable throwable) { 378 | callback.onFailure(throwable); 379 | } 380 | })); 381 | } 382 | 383 | /** 384 | * Fetch messages 385 | *

386 | * Asynchronously send the request and notify {@code callback} of its response or if an error 387 | * occurred 388 | *

389 | * @param callback 390 | */ 391 | public void fetchMessages(Callback> callback) { 392 | fetchMessages(0, callback); 393 | } 394 | 395 | // -> Fetch Subscription 396 | 397 | /** 398 | * Fetch subscriptions 399 | *

400 | * Synchronously send the request and return its response. 401 | *

402 | * @param shortCode 403 | * @param keyword 404 | * @param lastReceivedId 405 | * @return 406 | * @throws IOException 407 | */ 408 | public List fetchSubscriptions(String shortCode, String keyword, long lastReceivedId) throws IOException { 409 | Response resp = premiumSMS.fetchSubscriptions(mUsername, shortCode, keyword, lastReceivedId).execute(); 410 | if (!resp.isSuccessful()) { 411 | throw new IOException(resp.errorBody().string()); 412 | } 413 | return resp.body().subscriptions; 414 | } 415 | 416 | /** 417 | * Fetch subscription 418 | *

419 | * Asynchronously send the request and notify {@code callback} of its response or if an error 420 | * occurred 421 | *

422 | * @param shortCode 423 | * @param keyword 424 | * @param lastReceivedId 425 | * @param callback 426 | */ 427 | public void fetchSubscriptions(String shortCode, String keyword, long lastReceivedId, final Callback> callback) { 428 | premiumSMS.fetchSubscriptions(mUsername, shortCode, keyword, lastReceivedId).enqueue(makeCallback(new Callback() { 429 | @Override 430 | public void onSuccess(FetchSubscriptionResponse data) { 431 | callback.onSuccess(data.subscriptions); 432 | } 433 | 434 | @Override 435 | public void onFailure(Throwable throwable) { 436 | callback.onFailure(throwable); 437 | } 438 | })); 439 | 440 | } 441 | 442 | /** 443 | * Fetch subscriptions 444 | *

445 | * Synchronously send the request and return its response. 446 | *

447 | * @param shortCode 448 | * @param keyword 449 | * @return 450 | * @throws IOException 451 | */ 452 | public List fetchSubscriptions(String shortCode, String keyword) throws IOException { 453 | return fetchSubscriptions(shortCode, keyword, 0); 454 | } 455 | 456 | 457 | /** 458 | * Create subscription 459 | *

460 | * Asynchronously send the request and notify {@code callback} of its response or if an error 461 | * occurred 462 | *

463 | * @param shortCode 464 | * @param keyword 465 | * @param callback 466 | */ 467 | public void fetchSubscriptions(String shortCode, String keyword, Callback> callback) { 468 | fetchSubscriptions(shortCode, keyword, 0, callback); 469 | } 470 | 471 | // -> Create subscription 472 | 473 | /** 474 | * Create subscription 475 | *

476 | * Synchronously send the request and return its response. 477 | *

478 | * @param shortCode 479 | * @param keyword 480 | * @param phoneNumber 481 | * @return 482 | * @throws IOException 483 | */ 484 | public SubscriptionResponse createSubscription(String shortCode, String keyword, String phoneNumber) throws IOException { 485 | checkPhoneNumber(phoneNumber); 486 | Response resp = premiumSMS.createSubscription(mUsername, shortCode, keyword, phoneNumber).execute(); 487 | if (!resp.isSuccessful()) { 488 | throw new IOException(resp.errorBody().string()); 489 | } 490 | return resp.body(); 491 | } 492 | 493 | /** 494 | * Create subscription 495 | *

496 | * Asynchronously send the request and notify {@code callback} of its response or if an error 497 | * occurred 498 | *

499 | * @param shortCode 500 | * @param keyword 501 | * @param phoneNumber 502 | * @param callback 503 | */ 504 | public void createSubscription(String shortCode, String keyword, String phoneNumber, Callback callback) { 505 | try { 506 | checkPhoneNumber(phoneNumber); 507 | premiumSMS.createSubscription(mUsername, shortCode, keyword, phoneNumber).enqueue(makeCallback(callback)); 508 | } catch (IOException ex) { 509 | callback.onFailure(ex); 510 | } 511 | } 512 | 513 | // -> Create subscription 514 | 515 | /** 516 | * Delete subscription 517 | *

518 | * Synchronously send the request and return its response. 519 | *

520 | * @param shortCode 521 | * @param keyword 522 | * @param phoneNumber 523 | * @return 524 | * @throws IOException 525 | */ 526 | public SubscriptionResponse deleteSubscription(String shortCode, String keyword, String phoneNumber) throws IOException { 527 | checkPhoneNumber(phoneNumber); 528 | Response resp = premiumSMS.deleteSubscription(mUsername, shortCode, keyword, phoneNumber).execute(); 529 | if (!resp.isSuccessful()) { 530 | throw new IOException(resp.errorBody().string()); 531 | } 532 | return resp.body(); 533 | } 534 | 535 | /** 536 | * Delete subscription 537 | *

538 | * Asynchronously send the request and notify {@code callback} of its response or if an error 539 | * occurred 540 | *

541 | * @param shortCode 542 | * @param keyword 543 | * @param phoneNumber 544 | * @param callback 545 | */ 546 | public void deleteSubscription(String shortCode, String keyword, String phoneNumber, Callback callback) { 547 | try { 548 | checkPhoneNumber(phoneNumber); 549 | premiumSMS.deleteSubscription(mUsername, shortCode, keyword, phoneNumber).enqueue(makeCallback(callback)); 550 | } catch (IOException ex) { 551 | callback.onFailure(ex); 552 | } 553 | } 554 | 555 | } 556 | --------------------------------------------------------------------------------