【其他】【支付】【1】M-pesa(非洲肯尼亚的支付方式)
前言:
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:用户名,支付手机号等(传过来的手机号格式为 区号手机号,前面不要加+号,中间不要加横杠。254777777777) * 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; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架