i have intergrated daraja api with by booking app coded in java but in payments page it toasts an error message Error:Failed to initiate payment

16 views Asked by At
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.

0

There are 0 answers