对接支付宝的app接口

最近在做一些支付宝相关的支付后台实现,主要用的是Spring boot框架。因为支付宝老版的文档已经十分不详细(新版的也说的不清楚),所以我踩了无数坑,决定写篇博客来帮助后来人。

关于新版支付宝支付,需要APPID,也就是说必须在支付宝那儿建立应用并上线,还必须签约APP支付开通这个服务才能用。

但是本次我使用的是老版APP支付接口,所以并不需要APPID,只要签约支付宝开通APP支付服务就行。

关于流程,前端使用的是DCcloud的H5框架,集成了支付宝客户端SDK,整个流程如下:

用户点击支付按钮->客户端调用自己服务端API->自己服务端生成支付orderinfo(字符串)返回客户端->客户端拿orderinfo调用支付宝客户端SDK->SDK自启动支付宝APP完成支付->支付宝服务端给自己服务端发送异步通知->用户在异步通知验证支付成功后对自己数据库进行操作。

第一步:生成私钥公钥。这个可以用工具生成,需要去下载一键生成RSA**工具。下载地址:https://doc.open.alipay.com/docs/doc.htm?spm=a219a.7629140.0.0.pohUoc&treeId=291&articleId=106097&docType=1

第二步:在支付宝网站上上传你的公钥:

对接支付宝的app接口

为防万一,请将开放平台**,mapi网关产品**,老版wap支付**全部配上你自己生成的公钥。

支付宝异步通知你的时候,请使用mapi网关产品**的支付宝公钥进行验证。

第三步:服务端如何生成orderinfo?不多说直接上代码:

 

 
  1. package com.ibm.callcenter.controller;

  2.  
  3. import com.alipay.api.AlipayApiException;

  4. import com.alipay.api.AlipayClient;

  5. import com.alipay.api.DefaultAlipayClient;

  6. import com.alipay.api.domain.AlipayTradeAppPayModel;

  7. import com.alipay.api.internal.util.AlipaySignature;

  8. import com.alipay.api.request.AlipayTradeAppPayRequest;

  9. import com.alipay.api.response.AlipayTradeAppPayResponse;

  10. import org.springframework.stereotype.Controller;

  11. import org.springframework.ui.Model;

  12. import org.springframework.web.bind.annotation.RequestMapping;

  13. import org.springframework.web.bind.annotation.RequestMethod;

  14. import org.springframework.web.bind.annotation.RequestParam;

  15. import org.springframework.web.bind.annotation.ResponseBody;

  16.  
  17. import com.ibm.callcenter.util.SignUtils;

  18. import javax.servlet.http.HttpServletRequest;

  19. import javax.servlet.http.HttpServletResponse;

  20. import java.io.UnsupportedEncodingException;

  21. import java.net.URLEncoder;

  22. import java.text.SimpleDateFormat;

  23. import java.util.Date;

  24. import java.util.HashMap;

  25. import java.util.Iterator;

  26. import java.util.Map;

  27.  
  28. @Controller

  29. @RequestMapping(value="/alipay")

  30. @ResponseBody

  31. public class test {

  32.  
  33. @RequestMapping(value = "/pay")

  34. public String pay(//@RequestParam("fee") String total_fee,

  35. HttpServletResponse response1) {

  36.  
  37. Date now = new Date();

  38. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");//可以方便地修改日期格式

  39. String hehe = dateFormat.format( now );

  40. System.out.println(hehe+"test123");

  41. String out_strade_no=hehe+"wentest"; //自己写的订单编号

  42. String private_key="写上你自己生成的与上传公钥相对应的私钥";

  43. String partner="写上你自己的UID(PID)";

  44. String seller_id="写上你支付宝收款的账号";

  45. String body="aiyoubucuo";

  46. String notify_url="写上你异步通知的接口地址,切记需要是外网地址";

  47. String show_url="https://www.baidu.com/"; //支付宝同步跳转地址

  48. String total_fee="0.01"; //支付金额

  49. String subject="test";

  50. String order="service=\"mobile.securitypay.pay\"&partner=\""+partner+"\"&_input_charset=\"UTF-8\"&out_trade_no=\""+out_strade_no+"\"&subject=\""+subject+"\"&payment_type=\"1\"&seller_id=\""+seller_id+"\"&total_fee=\""+total_fee+"\"&body=\""+body+"\"&it_b_pay=\"1d\"¬ify_url=\""+notify_url+"\"&show_url=\""+show_url+"\"";

  51. String sign=SignUtils.sign(order,private_key,false);

  52. try {

  53. // 仅需对sign 做URL编码

  54. sign = URLEncoder.encode(sign, "UTF-8");

  55. } catch (UnsupportedEncodingException e) {

  56. e.printStackTrace();

  57. }

  58. final String result = order + "&sign=\"" + sign + "\"&"

  59. + getSignType();

  60. System.err.println("123"+result);

  61. return result;

  62. }

  63.  
  64. public String getSignType() {

  65. return "sign_type=\"RSA\"";

  66. }

  67. /**

  68. * 异步通知付款状态的Controller

  69. * @param request

  70. * @param response

  71. * @return

  72. */

  73. @SuppressWarnings("rawtypes")

  74. @RequestMapping(value="/notify",method = RequestMethod.POST )

  75. public String notify(HttpServletRequest request,

  76. HttpServletResponse response) throws AlipayApiException {

  77.  
  78. Map<String,String> params = new HashMap<String,String>();

  79. Map requestParams = request.getParameterMap();

  80. for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {

  81. String name = (String) iter.next();

  82. String[] values = (String[]) requestParams.get(name);

  83. String valueStr = "";

  84. for (int i = 0; i < values.length; i++) {

  85. valueStr = (i == values.length - 1) ? valueStr + values[i]: valueStr + values[i] + ",";

  86. }

  87. params.put(name, valueStr);

  88. }

  89. String CHARSET = "UTF-8";

  90. String ALIPAY_PUBLIC_KEY="写上你支付宝生成的公钥";

  91. String tradeNo = request.getParameter("out_trade_no");

  92. String tradeStatus = request.getParameter("trade_status");

  93. //String notifyId = request.getParameter("notify_id");

  94. //System.err.println(params.toString());

  95. boolean flag = AlipaySignature.rsaCheckV1(params,ALIPAY_PUBLIC_KEY, CHARSET,"RSA");

  96.  
  97. if(flag){//验证成功

  98.  
  99. if(tradeStatus.equals("TRADE_FINISHED") || tradeStatus.equals("TRADE_SUCCESS")) {

  100. //要写的逻辑。自己按自己的要求写

  101. // log.error("ok.......");

  102. System.err.println(">>>>>充值成功" + tradeNo);

  103. }

  104.  
  105. return "success";

  106. }else{//验证失败

  107. System.out.println("fail yanzheng");

  108.  
  109. return "web/pay/fail";

  110. }

  111.  
  112. }

  113.  
  114. }

把生成签名的两个需要依赖的类也给大家:

 

 
  1. package com.ibm.callcenter.util;

  2.  
  3. import java.security.KeyFactory;

  4. import java.security.PrivateKey;

  5. import java.security.spec.PKCS8EncodedKeySpec;

  6.  
  7. public class SignUtils {

  8.  
  9. private static final String ALGORITHM = "RSA";

  10.  
  11. private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

  12.  
  13. private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";

  14.  
  15. private static final String DEFAULT_CHARSET = "UTF-8";

  16.  
  17. private static String getAlgorithms(boolean rsa2) {

  18. return rsa2 ? SIGN_SHA256RSA_ALGORITHMS : SIGN_ALGORITHMS;

  19. }

  20.  
  21. public static String sign(String content, String privateKey, boolean rsa2) {

  22. try {

  23. PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(

  24. Base64.decode(privateKey));

  25. KeyFactory keyf = KeyFactory.getInstance(ALGORITHM);

  26. PrivateKey priKey = keyf.generatePrivate(priPKCS8);

  27.  
  28. java.security.Signature signature = java.security.Signature

  29. .getInstance(getAlgorithms(rsa2));

  30.  
  31. signature.initSign(priKey);

  32. signature.update(content.getBytes(DEFAULT_CHARSET));

  33.  
  34. byte[] signed = signature.sign();

  35.  
  36. return Base64.encode(signed);

  37. } catch (Exception e) {

  38. e.printStackTrace();

  39. }

  40.  
  41. return null;

  42. }

  43.  
  44. }

以及这个:

 

 
  1. package com.ibm.callcenter.util;

  2.  
  3. public final class Base64 {

  4.  
  5. private static final int BASELENGTH = 128;

  6. private static final int LOOKUPLENGTH = 64;

  7. private static final int TWENTYFOURBITGROUP = 24;

  8. private static final int EIGHTBIT = 8;

  9. private static final int SIXTEENBIT = 16;

  10. private static final int FOURBYTE = 4;

  11. private static final int SIGN = -128;

  12. private static char PAD = '=';

  13. private static byte[] base64Alphabet = new byte[BASELENGTH];

  14. private static char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];

  15.  
  16. static {

  17. for (int i = 0; i < BASELENGTH; ++i) {

  18. base64Alphabet[i] = -1;

  19. }

  20. for (int i = 'Z'; i >= 'A'; i--) {

  21. base64Alphabet[i] = (byte) (i - 'A');

  22. }

  23. for (int i = 'z'; i >= 'a'; i--) {

  24. base64Alphabet[i] = (byte) (i - 'a' + 26);

  25. }

  26.  
  27. for (int i = '9'; i >= '0'; i--) {

  28. base64Alphabet[i] = (byte) (i - '0' + 52);

  29. }

  30.  
  31. base64Alphabet['+'] = 62;

  32. base64Alphabet['/'] = 63;

  33.  
  34. for (int i = 0; i <= 25; i++) {

  35. lookUpBase64Alphabet[i] = (char) ('A' + i);

  36. }

  37.  
  38. for (int i = 26, j = 0; i <= 51; i++, j++) {

  39. lookUpBase64Alphabet[i] = (char) ('a' + j);

  40. }

  41.  
  42. for (int i = 52, j = 0; i <= 61; i++, j++) {

  43. lookUpBase64Alphabet[i] = (char) ('0' + j);

  44. }

  45. lookUpBase64Alphabet[62] = (char) '+';

  46. lookUpBase64Alphabet[63] = (char) '/';

  47.  
  48. }

  49.  
  50. private static boolean isWhiteSpace(char octect) {

  51. return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);

  52. }

  53.  
  54. private static boolean isPad(char octect) {

  55. return (octect == PAD);

  56. }

  57.  
  58. private static boolean isData(char octect) {

  59. return (octect < BASELENGTH && base64Alphabet[octect] != -1);

  60. }

  61.  
  62. /**

  63. * Encodes hex octects into Base64

  64. *

  65. * @param binaryData

  66. * Array containing binaryData

  67. * @return Encoded Base64 array

  68. */

  69. public static String encode(byte[] binaryData) {

  70.  
  71. if (binaryData == null) {

  72. return null;

  73. }

  74.  
  75. int lengthDataBits = binaryData.length * EIGHTBIT;

  76. if (lengthDataBits == 0) {

  77. return "";

  78. }

  79.  
  80. int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;

  81. int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;

  82. int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1

  83. : numberTriplets;

  84. char encodedData[] = null;

  85.  
  86. encodedData = new char[numberQuartet * 4];

  87.  
  88. byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;

  89.  
  90. int encodedIndex = 0;

  91. int dataIndex = 0;

  92.  
  93. for (int i = 0; i < numberTriplets; i++) {

  94. b1 = binaryData[dataIndex++];

  95. b2 = binaryData[dataIndex++];

  96. b3 = binaryData[dataIndex++];

  97.  
  98. l = (byte) (b2 & 0x0f);

  99. k = (byte) (b1 & 0x03);

  100.  
  101. byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)

  102. : (byte) ((b1) >> 2 ^ 0xc0);

  103. byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)

  104. : (byte) ((b2) >> 4 ^ 0xf0);

  105. byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6)

  106. : (byte) ((b3) >> 6 ^ 0xfc);

  107.  
  108. encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];

  109. encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];

  110. encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];

  111. encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];

  112. }

  113.  
  114. // form integral number of 6-bit groups

  115. if (fewerThan24bits == EIGHTBIT) {

  116. b1 = binaryData[dataIndex];

  117. k = (byte) (b1 & 0x03);

  118.  
  119. byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)

  120. : (byte) ((b1) >> 2 ^ 0xc0);

  121. encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];

  122. encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];

  123. encodedData[encodedIndex++] = PAD;

  124. encodedData[encodedIndex++] = PAD;

  125. } else if (fewerThan24bits == SIXTEENBIT) {

  126. b1 = binaryData[dataIndex];

  127. b2 = binaryData[dataIndex + 1];

  128. l = (byte) (b2 & 0x0f);

  129. k = (byte) (b1 & 0x03);

  130.  
  131. byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2)

  132. : (byte) ((b1) >> 2 ^ 0xc0);

  133. byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4)

  134. : (byte) ((b2) >> 4 ^ 0xf0);

  135.  
  136. encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];

  137. encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];

  138. encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];

  139. encodedData[encodedIndex++] = PAD;

  140. }

  141.  
  142. return new String(encodedData);

  143. }

  144.  
  145. /**

  146. * Decodes Base64 data into octects

  147. *

  148. * @param encoded

  149. * string containing Base64 data

  150. * @return Array containind decoded data.

  151. */

  152. public static byte[] decode(String encoded) {

  153.  
  154. if (encoded == null) {

  155. return null;

  156. }

  157.  
  158. char[] base64Data = encoded.toCharArray();

  159. // remove white spaces

  160. int len = removeWhiteSpace(base64Data);

  161.  
  162. if (len % FOURBYTE != 0) {

  163. return null;// should be divisible by four

  164. }

  165.  
  166. int numberQuadruple = (len / FOURBYTE);

  167.  
  168. if (numberQuadruple == 0) {

  169. return new byte[0];

  170. }

  171.  
  172. byte decodedData[] = null;

  173. byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;

  174. char d1 = 0, d2 = 0, d3 = 0, d4 = 0;

  175.  
  176. int i = 0;

  177. int encodedIndex = 0;

  178. int dataIndex = 0;

  179. decodedData = new byte[(numberQuadruple) * 3];

  180.  
  181. for (; i < numberQuadruple - 1; i++) {

  182.  
  183. if (!isData((d1 = base64Data[dataIndex++]))

  184. || !isData((d2 = base64Data[dataIndex++]))

  185. || !isData((d3 = base64Data[dataIndex++]))

  186. || !isData((d4 = base64Data[dataIndex++]))) {

  187. return null;

  188. }// if found "no data" just return null

  189.  
  190. b1 = base64Alphabet[d1];

  191. b2 = base64Alphabet[d2];

  192. b3 = base64Alphabet[d3];

  193. b4 = base64Alphabet[d4];

  194.  
  195. decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);

  196. decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));

  197. decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

  198. }

  199.  
  200. if (!isData((d1 = base64Data[dataIndex++]))

  201. || !isData((d2 = base64Data[dataIndex++]))) {

  202. return null;// if found "no data" just return null

  203. }

  204.  
  205. b1 = base64Alphabet[d1];

  206. b2 = base64Alphabet[d2];

  207.  
  208. d3 = base64Data[dataIndex++];

  209. d4 = base64Data[dataIndex++];

  210. if (!isData((d3)) || !isData((d4))) {// Check if they are PAD characters

  211. if (isPad(d3) && isPad(d4)) {

  212. if ((b2 & 0xf) != 0)// last 4 bits should be zero

  213. {

  214. return null;

  215. }

  216. byte[] tmp = new byte[i * 3 + 1];

  217. System.arraycopy(decodedData, 0, tmp, 0, i * 3);

  218. tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);

  219. return tmp;

  220. } else if (!isPad(d3) && isPad(d4)) {

  221. b3 = base64Alphabet[d3];

  222. if ((b3 & 0x3) != 0)// last 2 bits should be zero

  223. {

  224. return null;

  225. }

  226. byte[] tmp = new byte[i * 3 + 2];

  227. System.arraycopy(decodedData, 0, tmp, 0, i * 3);

  228. tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);

  229. tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));

  230. return tmp;

  231. } else {

  232. return null;

  233. }

  234. } else { // No PAD e.g 3cQl

  235. b3 = base64Alphabet[d3];

  236. b4 = base64Alphabet[d4];

  237. decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);

  238. decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));

  239. decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);

  240.  
  241. }

  242.  
  243. return decodedData;

  244. }

  245.  
  246. /**

  247. * remove WhiteSpace from MIME containing encoded Base64 data.

  248. *

  249. * @param data

  250. * the byte array of base64 data (with WS)

  251. * @return the new length

  252. */

  253. private static int removeWhiteSpace(char[] data) {

  254. if (data == null) {

  255. return 0;

  256. }

  257.  
  258. // count characters that's not whitespace

  259. int newSize = 0;

  260. int len = data.length;

  261. for (int i = 0; i < len; i++) {

  262. if (!isWhiteSpace(data[i])) {

  263. data[newSize++] = data[i];

  264. }

  265. }

  266. return newSize;

  267. }

  268. }

代码与业务逻辑应该很清楚了。有不懂的同学可以评论问我,我再做补充。