【其他】【支付】【1】M-pesa(非洲肯尼亚的支付方式)

2019-08-18

前言:

M-pesa:肯尼亚移动运营商Safaricom推出的手机银行业务。是依托于手机SIM卡进行支付的。

官网:https://www.safaricom.co.ke/dealers/login.php

开发者网站:https://developer.safaricom.co.ke/docs#going-live

正文:

业务背景:公司需要在官网上加一个注册为经销商的功能,其中有一个环节就是需要用户支付一定的金钱。业务范围是在肯尼亚。

流程:

需要先在肯尼亚那边注册一个商户账号,那边审批也要一定的时间,所以要提早去申请。通过之后,我们可以拿到商户编号,密钥等信息,它是有正式接口和测试接口的,可以先用测试接口调试。

后台需要写三个接口,一个是给前端的信息,一个是发送支付请求给Mpesa,一个是获取Mpesa的支付状态。

前端需要在用户点击支付之后调用后台的支付接口,然后不断地请求后台的支付状态接口,直到获取到状态为止。

用户那边的表现形式为,手机上会收到一个支付的弹窗,会有金额数和商户名称,用户输入密码即可完成支付。用户支付超时,弹窗还在,此时输入支付密码是不会支付成功的。在弹窗关闭后,用户在手机上是无法主动再次调出弹窗的,也没有相关的短信。

代码:

//支付相关
@Service
public class PaymentService {
    public static Logger logger = Logger.getLogger(PaymentService.class.getName());
    @Autowired private IDistributorDao distributorDao; //更新经销商信息
    @Autowired private IPaymentBillDao paymentBillDao; //记录订单流水
    
    /** 给前端显示的商户信息
     *  paymentVo:用户名,支付手机号等
     *  PaybillVo:返回给前端的信息,订单号,金额等
     */
    public ResultComplete<PaybillVo> getPaybill(PaymentVo paymentVo) {    
        logger.info("getPaybill begin; info:%s" + paymentVo.toString());
        PaybillVo info = new PaybillVo();
        info.setPaybill(MpesaConfig.COMPANY_NAME); //商户名称(与用户弹窗内的显示一致)
        info.setPayAmount(BaseView.PAY_AMOUNT); //金额(货币类型为肯尼亚先令)
        info.setOrderNo(this.getOrderNo()); //获取订单号
        
        //处理支付流水。。。记录相关信息到支付流水表
        return new ResultComplete<>(info); 
    }

    //支付接口
    public ResultComplete<String> payment(String payPhone, String orderNo) throws IOException {
        String amount = BaseView.PAY_AMOUNT;
        logger.info(String.format("payment begin; params: payPhone:%s, orderNo:%s", amount, payPhone, orderNo));
        //调用M-pesa接口
        String payInfo = Mpesa.STKPushSimulation(amount, payPhone, orderNo);

//TODO  test 测试的时候,因为我在国内,没有办法支付,所以把数据写死了。
// 由于是改造旧项目,工期比较紧,所以没有进行配置,直接是注释掉了,大家有时间可以优化一下,这里仅做参考
//        String payInfo = "{"
//                + "\"MerchantRequestID\":\"6809-2590977-1\","
//                + "\"CheckoutRequestID\":\"test\","
//                + "\"ResponseCode\": \"0\""
//                + "}";
        
        Gson gson = new Gson();
        Map map = gson.fromJson(payInfo, Map.class);
        String responseCode = (String) map.get("ResponseCode");
        String checkoutRequestId = (String) map.get("CheckoutRequestID");

        if (responseCode == null || Integer.parseInt(responseCode) != 0) {
            logger.error("payment request fail");
            String payResultCode = responseCode;
            String payResultDesc = (String) map.get("CustomerMessage");            
            if (StringUtils.isBlank(responseCode)) {
                payResultCode = (String) map.get("errorCode");
                payResultDesc = (String) map.get("errorMessage");
            }
            
            //支付请求失败时更新支付流水表。。。
            return new ResultComplete<>(false, 101, "支付请求失败", null);
        }
        
        //由于是旧项目的缘故,我用的session进行校验。其实不是很方便,可改为存到Redis
        HttpSession session = HttpHelper.getSession();
        session.setAttribute(BaseView.SESSION_PAY_KEY + orderNo, payInfo);
        session.setMaxInactiveInterval(0);
        //支付请求成功时更新支付流水表。。。
        logger.info("payment end");
        
        return new ResultComplete<>(null);
    }

    //前端调用支付状态
    public ResultComplete<String> getPaymentStatus(String orderNo) throws IOException {    
        logger.info(String.format("getPaymentStatus begin; params: orderNo:%s", orderNo));
//TODO test        
        String checkoutRequestId = getCheckoutRequestId(orderNo); //获取支付请求流水号
        if (StringUtils.isBlank(checkoutRequestId)) {
            return new ResultComplete<>(false, 101, "未获取支付请求信息", null);
        }
        
        String statusInfo = Mpesa.STKPushTransactionStatus(checkoutRequestId);
        
//        String statusInfo = "{"
//                + "\"MerchantRequestID\":\"6809-2590977-1\","
//                + "\"CheckoutRequestID\":\"test\","
//                + "\"ResponseCode\": \"0\","
//                + "\"ResultCode\": \"0\","
//                + "\"ResultDesc\": \"Success\""
//                + "}";
        
        Gson gson = new Gson();
        Map map = gson.fromJson(statusInfo, Map.class);
        String responseCode = (String) map.get("ResponseCode");
        String resultCode = (String) map.get("ResultCode");
        String resultDesc = (String) map.get("ResultDesc");      

        if (responseCode == null || Integer.parseInt(responseCode) != 0 
                || resultCode == null || Integer.parseInt(resultCode) != 0) {
            logger.error("payment status fail");
            String errorCode = (String) map.get("errorCode");
            String errorMessage = (String) map.get("errorMessage");
            if (errorCode != null && errorCode.equals("500.001.1001")) {
                return new ResultComplete<>(true, 201, "支付正在处理中", null);
            }
            
            String payResultCode = StringUtils.isNotBlank(resultCode) ? resultCode : errorCode;
            String payResultDesc = StringUtils.isNotBlank(resultDesc) ? resultDesc : errorMessage;
            
            //更新流水
            return new ResultComplete<>(false, 102, "支付失败", null);
        }
        
        HttpSession session = HttpHelper.getSession();
        session.setAttribute(BaseView.SESSION_PAY_STATUS_KEY + orderNo, statusInfo);
        session.setMaxInactiveInterval(0);
        //更新流水
        logger.info("getPaymentStatus end");
        
        return new ResultComplete<>(null);
    }
    

    //获取订单号(我定义的是PAY-20190817-000001这样,可自己视情况而定)
    private String getOrderNo() {
        String nowDate = CalendarUtil.DtoSYmd(new Date());
        String lastOrderNo = this.paymentBillDao.getLastOrderNo(nowDate);
        String orderNo = "PAY-" + nowDate + "-000001";
        
        if (StringUtils.isNotBlank(lastOrderNo)) {
           String numberFmt = lastOrderNo.substring(lastOrderNo.length() - 6); //取最大编号后6位;
           String end = String.format("%06d", Integer.parseInt(numberFmt) + 1);
            
            orderNo = "PAY-" + nowDate + "-" + end;
        }
        return orderNo;
    }
    
    //获取支付请求流水号
    private String getCheckoutRequestId(String orderNo) {
        HttpSession session = HttpHelper.getSession();
        String payInfo = String.valueOf(session.getAttribute(BaseView.SESSION_PAY_KEY + orderNo));
        if (StringUtils.isBlank(payInfo) || payInfo.equals("null")) {
            return "";
        }
        
        Gson gson = new Gson();
        Map map = gson.fromJson(payInfo, Map.class);
        String responseCode = (String) map.get("ResponseCode");

        if (responseCode == null || Integer.parseInt(responseCode) != 0) {
            return "";
        }
        return (String) map.get("CheckoutRequestID");
    }
}

 MpesaConfig(可以考虑写在配置文件)

public class MpesaConfig {

    /**
     * Connection timeout duration
     */
    public static final int CONNECT_TIMEOUT = 60 * 1000;
    /**
     * Connection Read timeout duration
     */
    public static final int READ_TIMEOUT = 60 * 1000;
    /**
     * Connection write timeout duration
     */
    public static final int WRITE_TIMEOUT = 60 * 1000;
    
    /**
     * global topic to receive app wide push notifications
     */
    public static final String TOPIC_GLOBAL = "global";

    // broadcast receiver intent filters
    public static final String REGISTRATION_COMPLETE = "registrationComplete";
    public static final String PUSH_NOTIFICATION = "pushNotification";

    // id to handle the notification in the notification tray
    public static final int NOTIFICATION_ID = 100;
    public static final int NOTIFICATION_ID_BIG_IMAGE = 101;
    public static final String SHARED_PREF = "ah_firebase";

    //STKPush Properties
    //public static final String BASE_URL = "https://sandbox.safaricom.co.ke"; //测试用的Base URL
    public static final String BASE_URL = "https://api.safaricom.co.ke"; //Base URL
    public static final String BUSINESS_SHORT_CODE = ""; //商户编号
    public static final String PASSKEY = "";
    public static final String TRANSACTION_TYPE = "CustomerPayBillOnline";
    public static final String PARTYB = ""; //收款帐号(一般与商户编号一致)
    // 付款回调url ,验证付款发送请求是否成功(非必须)
    public static final String CALLBACKURL = "http://www.*.com/pay/request/info?orderNo=";

    //密钥
    public static final String CONSUMER_KEY = "";
    //secret
    public static final String CONSUMER_SECRET = "";
    
    public static String AccessToken = null; //根据密钥和secret获得,是有有效期的

}

Mpesa:

package com.mpesa;

import java.io.IOException;
import java.util.Base64;

import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.mpesa.STKPush;
import com.mpesa.STKPushTransactionStatusBean;

import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import com.mpesa.MpesaUtils;


import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;

public class Mpesa {
    public static Logger logger = Logger.getLogger(Mpesa.class.getName());
    
    static String appKey = MpesaConfig.CONSUMER_KEY;
    static String appSecret = MpesaConfig.CONSUMER_SECRET;
//    static String accessToken;
    
//    public Mpesa(String app_key, String app_secret) { 
//        appKey = app_key;
//        appSecret = app_secret; 
//    }

    /**
     * 获取 AccessToken
     *
     * @return
     * @throws IOException
     */
    public static String authenticate() throws IOException {
        logger.info("authenticate begin");
        String app_key = appKey;
        String app_secret = appSecret;
        String appKeySecret = app_key + ":" + app_secret;
        byte[] bytes = appKeySecret.getBytes("ISO-8859-1");
        String encoded = Base64.getEncoder().encodeToString(bytes);

        OkHttpClient client = new OkHttpClient();

        Request request = new Request.Builder()
                .url(MpesaConfig.BASE_URL + "/oauth/v1/generate?grant_type=client_credentials").get()
                .addHeader("authorization", "Basic " + encoded).addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        JSONObject jsonObject = null;
        String accessToken = null;
        try {
            // jsonObject = new JSONObject(response.body().string());
            ResponseBody ss = response.body();
            String info = ss.string();
            logger.info("pay authenticate; info:%s" + info);

            jsonObject = JSONObject.fromObject(info);
            accessToken = jsonObject.getString("access_token");
            MpesaConfig.AccessToken = accessToken;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return accessToken;
    }

    public String C2BSimulation(String shortCode, String commandID, String amount, String MSISDN, String billRefNumber)
            throws IOException {    
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("ShortCode", shortCode);
            jsonObject.put("CommandID", commandID);
            jsonObject.put("Amount", amount);
            jsonObject.put("Msisdn", MSISDN);
            jsonObject.put("BillRefNumber", billRefNumber);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.add(jsonObject);

        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("C2BSimulation requestJson:" + requestJson);
        
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/safaricom/c2b/v1/simulate").post(body)
                .addHeader("content-type", "application/json").addHeader("authorization", "Bearer " + MpesaConfig.AccessToken)
                .addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("C2BSimulation end; info:" + info);

        return info;
    }

    public String B2CRequest(String initiatorName, String securityCredential, String commandID, String amount,
            String partyA, String partyB, String remarks, String queueTimeOutURL, String resultURL, String occassion)
            throws IOException {
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("InitiatorName", initiatorName);
            jsonObject.put("SecurityCredential", securityCredential);
            jsonObject.put("CommandID", commandID);
            jsonObject.put("Amount", amount);
            jsonObject.put("PartyA", partyA);
            jsonObject.put("PartyB", partyB);
            jsonObject.put("Remarks", remarks);
            jsonObject.put("QueueTimeOutURL", queueTimeOutURL);
            jsonObject.put("ResultURL", resultURL);
            jsonObject.put("Occassion", occassion);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.add(jsonObject);
        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("B2CRequest requestJson:" + requestJson);
        
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/mpesa/b2c/v1/paymentrequest").post(body)
                .addHeader("content-type", "application/json").addHeader("authorization", "Bearer " + MpesaConfig.AccessToken)
                .addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("B2CRequest end; info:" + info);
        
        return info;
    }

    public String B2BRequest(String initiatorName, String accountReference, String securityCredential, String commandID,
            String senderIdentifierType, String receiverIdentifierType, float amount, String partyA, String partyB,
            String remarks, String queueTimeOutURL, String resultURL, String occassion) throws IOException {
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("Initiator", initiatorName);
            jsonObject.put("SecurityCredential", securityCredential);
            jsonObject.put("CommandID", commandID);
            jsonObject.put("SenderIdentifierType", senderIdentifierType);
            jsonObject.put("RecieverIdentifierType", receiverIdentifierType);
            jsonObject.put("Amount", amount);
            jsonObject.put("PartyA", partyA);
            jsonObject.put("PartyB", partyB);
            jsonObject.put("Remarks", remarks);
            jsonObject.put("AccountReference", accountReference);
            jsonObject.put("QueueTimeOutURL", queueTimeOutURL);
            jsonObject.put("ResultURL", resultURL);
        } catch (JSONException e) {
            e.printStackTrace();
        }

        jsonArray.add(jsonObject);

        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("B2BRequest requestJson:" + requestJson);

        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/safaricom/b2b/v1/paymentrequest")
                .post(body).addHeader("content-type", "application/json")
                .addHeader("authorization", "Bearer " + MpesaConfig.AccessToken).addHeader("cache-control", "no-cache")

                .build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("B2BRequest end; info:" + info);
        
        return info;

    }

    /**
     * 
     * @param amount
     *            金额
     * @param phoneNumber
     *            手机号,
     * @param orderid
     *            订单id
     * @return
     * @throws IOException
     */
    public static String STKPushSimulation(String amount, String phoneNumber, String orderid) throws IOException {
        logger.info(String.format("pay request begin; amount:%s,phoneNumber:%s,orderid:%s", amount, phoneNumber, orderid));
        logger.info("MpesaConfig.AccessToken:" + MpesaConfig.AccessToken);
        MpesaConfig.AccessToken = authenticate();

        String timestamp = MpesaUtils.getTimestamp();
        STKPush stkPush = new STKPush(MpesaConfig.BUSINESS_SHORT_CODE,
                MpesaUtils.getPassword(MpesaConfig.BUSINESS_SHORT_CODE, MpesaConfig.PASSKEY, timestamp), timestamp,
                MpesaConfig.TRANSACTION_TYPE, String.valueOf(amount), MpesaUtils.sanitizePhoneNumber(phoneNumber),
                MpesaConfig.PARTYB, MpesaUtils.sanitizePhoneNumber(phoneNumber),
                MpesaConfig.CALLBACKURL + orderid + "&phone_number=" + phoneNumber, "BFSumaOnLine", "test");
        Gson gson = new Gson();
        String requestJson = gson.toJson(stkPush);
        OkHttpClient client = new OkHttpClient();
        String url = MpesaConfig.BASE_URL + "/mpesa/stkpush/v1/processrequest";
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(url).post(body).addHeader("content-type", "application/json")
                .addHeader("authorization", "Bearer " + MpesaConfig.AccessToken).addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("pay request end; info:" + info);
        
        return info;
    }

    /**
     * 查询在线订单状态
     * 
     * @param checkoutRequestID
     *            在线付款提交成功后返回 checkoutRequestID
     * @return
     * @throws IOException
     */
    public static String STKPushTransactionStatus(String checkoutRequestID) throws IOException {
        logger.info(String.format("pay status begin; checkoutRequestID:%s", checkoutRequestID));
        
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        String timestamp = MpesaUtils.getTimestamp();
        String password = MpesaUtils.getPassword(MpesaConfig.BUSINESS_SHORT_CODE, MpesaConfig.PASSKEY, timestamp);
        STKPushTransactionStatusBean bean = new STKPushTransactionStatusBean(MpesaConfig.BUSINESS_SHORT_CODE, password,
                timestamp, checkoutRequestID);
        Gson gson = new Gson();
        String requestJson = gson.toJson(bean);
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/mpesa/stkpushquery/v1/query").post(body)
                .addHeader("authorization", "Bearer " + MpesaConfig.AccessToken).addHeader("content-type", "application/json")
                .build();
        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("pay status end; info:" + info);

        return info;

    }

    public String reversal(String initiator, String securityCredential, String commandID, String transactionID,
            String amount, String receiverParty, String recieverIdentifierType, String resultURL,
            String queueTimeOutURL, String remarks, String ocassion) throws IOException {
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("Initiator", initiator);
            jsonObject.put("SecurityCredential", securityCredential);
            jsonObject.put("CommandID", commandID);
            jsonObject.put("TransactionID", transactionID);
            jsonObject.put("Amount", amount);
            jsonObject.put("ReceiverParty", receiverParty);
            jsonObject.put("RecieverIdentifierType", recieverIdentifierType);
            jsonObject.put("QueueTimeOutURL", queueTimeOutURL);
            jsonObject.put("ResultURL", resultURL);
            jsonObject.put("Remarks", remarks);
            jsonObject.put("Occasion", ocassion);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.add(jsonObject);

        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("reversal requestJson:" + requestJson);

        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/safaricom/reversal/v1/request").post(body)
                .addHeader("content-type", "application/json")
                .addHeader("authorization", "Bearer xNA3e9KhKQ8qkdTxJJo7IDGkpFNV")
                .addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("reversal end; info:" + info);

        return info;
    }

    public String balanceInquiry(String initiator, String commandID, String securityCredential, String partyA,
            String identifierType, String remarks, String queueTimeOutURL, String resultURL) throws IOException {
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("Initiator", initiator);
            jsonObject.put("SecurityCredential", securityCredential);
            jsonObject.put("CommandID", commandID);
            jsonObject.put("PartyA", partyA);
            jsonObject.put("IdentifierType", identifierType);
            jsonObject.put("Remarks", remarks);
            jsonObject.put("QueueTimeOutURL", queueTimeOutURL);
            jsonObject.put("ResultURL", resultURL);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.add(jsonObject);

        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("balanceInquiry requestJson:" + requestJson);

        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/safaricom/accountbalance/v1/query")
                .post(body).addHeader("content-type", "application/json")
                .addHeader("authorization", "Bearer fwu89P2Jf6MB1A2VJoouPg0BFHFM")
                .addHeader("cache-control", "no-cache")
                .addHeader("postman-token", "2aa448be-7d56-a796-065f-b378ede8b136").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("balanceInquiry end; info:" + info);
        
        return info;
    }

    public String registerURL(String shortCode, String responseType, String confirmationURL, String validationURL)
            throws IOException {
        if (MpesaConfig.AccessToken == null) {
            MpesaConfig.AccessToken = authenticate();
        }
        JSONArray jsonArray = new JSONArray();
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject.put("ShortCode", shortCode);
            jsonObject.put("ResponseType", responseType);
            jsonObject.put("ConfirmationURL", confirmationURL);
            jsonObject.put("ValidationURL", validationURL);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        jsonArray.add(jsonObject);
        String requestJson = jsonArray.toString().replaceAll("[\\[\\]]", "");
        logger.info("registerURL requestJson:" + requestJson);
        
        OkHttpClient client = new OkHttpClient();
        MediaType mediaType = MediaType.parse("application/json");
        RequestBody body = RequestBody.create(mediaType, requestJson);
        Request request = new Request.Builder().url(MpesaConfig.BASE_URL + "/mpesa/c2b/v1/registerurl").post(body)
                .addHeader("content-type", "application/json").addHeader("authorization", "Bearer " + MpesaConfig.AccessToken)
                .addHeader("cache-control", "no-cache").build();

        Response response = client.newCall(request).execute();
        String info = response.body().string();
        logger.info("registerURL end; info:" + info);
        
        return info;
    }
}

MpesaUtils:

package com.mpesa;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

import org.apache.tomcat.util.codec.binary.Base64;

/**
 * Created by miles on 23/11/2017.
 * Taken from https://github.com/bdhobare/mpesa-android-sdk
 */

public class MpesaUtils {

    public static String getTimestamp() {
        return new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()).format(new Date());
    }

    public static String sanitizePhoneNumber(String phone) {

        if (phone.equals("")) {
            return "";
        }

        if (phone.length() < 11 & phone.startsWith("0")) {
            String p = phone.replaceFirst("^0", "254");
            return p;
        }
        if (phone.length() == 13 && phone.startsWith("+")) {
            String p = phone.replaceFirst("^+", "");
            return p;
        }
        return phone;
    }

    public static String getPassword(String businessShortCode, String passkey, String timestamp) {
        String str = businessShortCode + passkey + timestamp;
        //encode the password to Base64
//        return Base64.encodeToString(str.getBytes(), Base64.NO_WRAP);
        return Base64.encodeBase64String(str.getBytes());

    }
}

STKPush

package com.mpesa;


/**
 * 在线付款请求参数
 * 
 */

public class STKPush {
/*    {
          "BusinessShortCode": "",
          "Password": "",
          "Timestamp": "",
          "TransactionType": "CustomerPayBillOnline",
          "Amount": "",
          "PartyA": "",
          "PartyB": "",
          "PhoneNumber": "",
          "CallBackURL": "",
          "AccountReference": "",
          "TransactionDesc": ""
        }
    
    */
    
    //收款方短码   组织短代码
    private String BusinessShortCode;
   // 在线支付密码  base64.encode(Shortcode + Passkey + Timestamp),passkey为在线支付密码
    private String Password;
    //时间戳  YYYYMMDDHHmmss
    private String Timestamp;
    // 付款类型   CustomerPayBillOnline(默认)    CustomerBuyGoodsOnline
    private String TransactionType;
//        金额  整数
    private String Amount;
    //  寄钱电话号码    付款方
    private String PartyA;
    // 收款方  ,与businessShortCode 相同
    private String PartyB;
    // 付款号码 2547***  12位    用于接收STK推送,与PartA 相同
    private String PhoneNumber;
    // 付款请求发送成功,mpesa服务器 发送消息付款推送到客户手机成功后,调用此链接 通知用户(自己服务器)
    private String CallBackURL;
    //账户备注  小于12个字符(字母和数字的任何组合)
    private String AccountReference;
    // 其他信息  小于13 字符,
    private String TransactionDesc;

    public STKPush(String businessShortCode, String password, String timestamp, String transactionType,
                   String amount, String partyA, String partyB, String phoneNumber, String callBackURL,
                   String accountReference, String transactionDesc) {
        this.BusinessShortCode = businessShortCode;
        this.Password = password;
        this.Timestamp = timestamp;
        this.TransactionType = transactionType;
        this.Amount = amount;
        this.PartyA = partyA;
        this.PartyB = partyB;
        this.PhoneNumber = phoneNumber;
        this.CallBackURL = callBackURL;
        this.AccountReference = accountReference;
        this.TransactionDesc = transactionDesc;
    }
}

STKPushTransactionStatusBean

package com.mpesa;
/**
 * 在线付款查询请求仓库
 * @author zzc
 *
 */
public class STKPushTransactionStatusBean {
    /*{
        "BusinessShortCode": " " ,  收款方断代码
        "Password": " ",  密钥
        "Timestamp": " ", 时间戳
        "CheckoutRequestID": " " 在线付款id
}*/
    private String BusinessShortCode;
    private String Password;
    private String Timestamp;
    private String CheckoutRequestID;
    public String getBusinessShortCode() {
        return BusinessShortCode;
    }
    public void setBusinessShortCode(String businessShortCode) {
        BusinessShortCode = businessShortCode;
    }
    public String getPassword() {
        return Password;
    }
    public void setPassword(String password) {
        Password = password;
    }
    public String getTimestamp() {
        return Timestamp;
    }
    public void setTimestamp(String timestamp) {
        Timestamp = timestamp;
    }
    public String getCheckoutRequestID() {
        return CheckoutRequestID;
    }
    public void setCheckoutRequestID(String checkoutRequestID) {
        CheckoutRequestID = checkoutRequestID;
    }
    public STKPushTransactionStatusBean(String businessShortCode, String password, String timestamp,
            String checkoutRequestID) {
        BusinessShortCode = businessShortCode;
        Password = password;
        Timestamp = timestamp;
        CheckoutRequestID = checkoutRequestID;
    }
    
    
}