package com.example.a4ntesaccoapp;
import android.util.Base64;
import androidx.annotation.NonNull;
import com.example.a4ntesaccoapp.Constants;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import okhttp3.*;
public class MpesaIntegration {
private static final MediaType JSON = MediaType.get("application/json");
private static final OkHttpClient client = new OkHttpClient();
public interface AccessTokenCallback {
void onAccessTokenReceived(String accessToken);
void onAccessTokenError(Throwable error);
}
public void processPayment(double amount, String phone, AccessTokenCallback callback) {
String formattedPhone = phone.replaceFirst("0", "254");
// Obtain the access token asynchronously
getAccessToken(new AccessTokenCallback() {
@Override
public void onAccessTokenReceived(String accessToken) {
if (accessToken != null) {
// Access token obtained, proceed with payment initiation
initiatePayment(amount, formattedPhone, accessToken, callback);
} else {
// Failed to obtain access token
callback.onAccessTokenError(new RuntimeException("Failed to obtain access token"));
}
}
@Override
public void onAccessTokenError(Throwable error) {
// Handle error while obtaining access token
callback.onAccessTokenError(error);
}
});
}
private void getAccessToken(AccessTokenCallback callback) {
String credentials = Constants.CONSUMER_KEY + ":" + Constants.CONSUMER_SECRET;
String encodedCredentials = Base64.encodeToString(credentials.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING | Base64.NO_WRAP);
RequestBody requestBody = RequestBody.create(null, new byte[0]);
Request request = new Request.Builder()
.url(Constants.TOKEN_URL)
.addHeader("Authorization", "Basic " + encodedCredentials)
.post(requestBody)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onAccessTokenError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful()) {
try {
JSONObject jsonResponse = new JSONObject(response.body().string());
String accessToken = jsonResponse.getString("access_token");
callback.onAccessTokenReceived(accessToken);
} catch (JSONException e) {
callback.onAccessTokenError(e);
}
} else {
callback.onAccessTokenError(new IOException("Failed to obtain access token"));
}
}
});
}
private void initiatePayment(double amount, String formattedPhone, String accessToken, AccessTokenCallback callback) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss", Locale.ENGLISH);
String timestamp = format.format(new Date());
String password = generatePassword(timestamp);
String requestBody = "{\n" +
" \"BusinessShortCode\": \"" + Constants.BUSINESS_CODE + "\",\n" +
" \"Password\": \"" + password + "\",\n" +
" \"Timestamp\": \"" + timestamp + "\",\n" +
" \"TransactionType\": \"CustomerPayBillOnline\",\n" +
" \"Amount\": \"" + amount + "\",\n" +
" \"PartyA\": \"" + formattedPhone + "\",\n" +
" \"PartyB\": \"" + Constants.BUSINESS_CODE + "\",\n" +
" \"PhoneNumber\": \"" + formattedPhone + "\",\n" +
" \"CallBackURL\": \"" + Constants.CALLBACK_URL + "\",\n" +
" \"AccountReference\": \"" + Constants.BUSINESS_CODE + "\",\n" +
" \"TransactionDesc\": \"Test\"\n" +
"}";
RequestBody body = RequestBody.create(requestBody, JSON);
Request request = new Request.Builder()
.url(Constants.STK_PUSH_URL)
.addHeader("Content-Type", "application/json")
.addHeader("Authorization", "Bearer " + accessToken)
.post(body)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
callback.onAccessTokenError(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
if (!response.isSuccessful()) {
callback.onAccessTokenError(new IOException("Failed to initiate payment"));
}
}
});
}
private String generatePassword(String timestamp) {
String password = Constants.BUSINESS_CODE + Constants.PASS_KEY + timestamp;
return Base64.encodeToString(password.getBytes(StandardCharsets.UTF_8), Base64.NO_PADDING | Base64.NO_WRAP);
}
}
package com.example.a4ntesaccoapp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.a4ntesaccoapp.MpesaIntegration.AccessTokenCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Locale;
public class PaymentActivity extends AppCompatActivity {
private EditText phoneNumberEditText;
private double amount; // Initialize amount variable
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_payment);
Intent intent = getIntent();
if (intent != null && intent.hasExtra("totalCost")) {
double totalCost = intent.getDoubleExtra("totalCost", 0.0);
amount = totalCost; // Initialize amount using totalCost
} else {
Toast.makeText(this, "Error: Amount not provided", Toast.LENGTH_SHORT).show();
finish(); // Finish the activity if amount is not provided
return;
}
phoneNumberEditText = findViewById(R.id.phoneNumberEditText);
Button proceedToPayButton = findViewById(R.id.proceedToPayButton);
TextView costTextView = findViewById(R.id.costTextView);
// Set the total cost in the TextView
costTextView.setText(String.format(Locale.ENGLISH, "%.2f", amount));
proceedToPayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String phoneNumber = phoneNumberEditText.getText().toString();
if (!isValidPhoneNumber(phoneNumber)) {
Toast.makeText(PaymentActivity.this, "Please enter a valid Phone number", Toast.LENGTH_SHORT).show();
return;
}
initiatePayment(phoneNumber);
}
});
}
private boolean isValidPhoneNumber(String phoneNumber) {
return phoneNumber.matches("[0-9]{10}");
}
private void initiatePayment(String phoneNumber) {
MpesaIntegration mpesaIntegration = new MpesaIntegration();
mpesaIntegration.processPayment(amount, phoneNumber, new AccessTokenCallback() {
@Override
public void onAccessTokenReceived(String accessToken) {
// Do nothing since payment process is handled internally
}
@Override
public void onAccessTokenError(Throwable error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(PaymentActivity.this, "Error: Failed to initiate payment", Toast.LENGTH_SHORT).show();
}
});
}
});
}
}
i am having payment page in my booking app that i am coding in java in android stusio. when i click proceed to pay button after filling the edittext with phone number i am getting a toast message instead of daraja sandbox. i believe the issue is in my codes but since i am a beginner in coding i cannot tell where the issue is.