基于spring-boot+uni-app实现app支付功能(微信/支付宝)服务端
支付宝支付
1 准备工作
申请支付能力
支付宝支付能力文档,按照文档开通过相应的支付能力
商家则根据文档提示进行实名并创建应用即可
添加需要的能力,签约
如果是个人开发者只能使用测试沙箱
接口加签方式
下载支付宝密匙生成器
打开后生成密匙—>打开文件位置—>上传到支付宝
上传完成之后还有一个授权回调地址没有设置,到写代码是再设置
2代码
依赖
<!-- 支付宝支付sdk -->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
<version>4.7.12.ALL</version>
</dependency>
<!-- 可能会用 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
支付宝支付配置类
支付宝证书地址, 支付宝用户私匙,支付宝用户公匙
私匙和公匙是使用支付宝开放平台助手生成的
支付宝证书地址是生成本地路径
package org.jeecg.modules.YGFutureXingGunMobile.YGFutureChat.entity;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Data
@Component
public class AlipayConfig {
@Value("${file.path.PayPath}")
private String payPath;//支付宝证书文件地址
/**
* 支付宝gatewayUrl
*/
private String gatewayUrl="https://openapi.alipay.com/gateway.do";
/**
* 商户应用id
*/
private String appid="11111111111111";
/**
* RSA私钥,用于对商户请求报文加签
*/
private String appPrivateKey="";//支付宝用户私匙
/**
* 支付宝RSA公钥,用于验签支付宝应答
*/
private String alipayPublicKey="";//支付宝用户公匙
/**
* 签名类型
*/
private String signType = "RSA2";
/**
* 格式
*/
private String formate = "json";
/**
* 编码
*/
private String charset = "UTF-8";
/**
* 同步地址
*/
private String returnUrl;
/**
* 异步地址
*/
private String notifyUrl;
/**
* 最大查询次数
*/
private static int maxQueryRetry = 5;
/**
* 查询间隔(毫秒)
*/
private static long queryDuration = 5000;
/**
* 最大撤销次数
*/
private static int maxCancelRetry = 3;
/**
* 撤销间隔(毫秒)
*/
private static long cancelDuration = 3000;
@Bean
public AlipayClient alipayClient(){
//构造client
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
//设置网关地址
certAlipayRequest.setServerUrl(this.gatewayUrl);
//设置应用Id
certAlipayRequest.setAppId(this.appid);
//设置应用私钥
certAlipayRequest.setPrivateKey(this.appPrivateKey);
//设置请求格式,固定值json
certAlipayRequest.setFormat("json");
//设置字符集
certAlipayRequest.setCharset(this.charset);
//设置签名类型
certAlipayRequest.setSignType(this.signType);
//设置应用公钥证书路径
certAlipayRequest.setCertPath(payPath+"appCertPublicKey_2021002198666681.crt");
//设置支付宝公钥证书路径
certAlipayRequest.setAlipayPublicCertPath(payPath+"alipayCertPublicKey_RSA2.crt");
//设置支付宝根证书路径
certAlipayRequest.setRootCertPath(payPath+"alipayRootCert.crt");
//构造client
AlipayClient alipayClient=null;
try {
alipayClient = new DefaultAlipayClient(certAlipayRequest);
} catch (AlipayApiException e) {
e.printStackTrace();
}
return alipayClient;
}
}
支付宝控制层
成功后拿到数据在app端可以直接唤起支付
//支付宝发送请求类 在支付宝配置类中通过@Bean 的方式交给spring管理
@Autowired
private AlipayClient alipayClient;
@Value("${server.notify}")
private String ip;//异步回调地址 当用户进行支付后,支付宝会调用这个地址
/**
*
* @param userId 用户id
* @param paymentType 付款方式
* @return
* @throws AlipayApiException
*/
@GetMapping("/alipay")
public String createOrder(String userId,String paymentType,String orderInfo) {
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setSubject("大乐透");//订单标题。
model.setOutTradeNo("70501111111S001111119");//商户网站唯一订单号
model.setTimeoutExpress("30m");//预付订单过期时间
model.setTotalAmount("1");//订单金额
model.setProductCode("QUICK_MSECURITY_PAY");//销售产品码,商家和支付宝签约的产品码
request.setBizModel(model);
request.setNotifyUrl(ip+"/pay/allipay/notify");
try {
//这里和普通的接口调用不同,使用的是sdkExecute
AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
return response.getBody();
} catch (AlipayApiException e) {
e.printStackTrace();
}
return "null";
}
/**
* 支付异步通知
* 接收到异步通知并验签通过后,一定要检查通知内容,
* 包括通知中的app_id、out_trade_no、total_amount是否与请求中的一致,并根据trade_status进行后续业务处理。
* https://docs.open.alipay.com/194/103296
*/
@RequestMapping("/allipay/notify")
public String notify(HttpServletRequest request) {
// 验证签名
boolean flag = payServiceImpl.rsaCheckV1(request);
if (flag) {
String tradeStatus = request.getParameter("trade_status"); // 交易状态
String outTradeNo = request.getParameter("out_trade_no"); // 商户订单号
String tradeNo = request.getParameter("trade_no"); // 支付宝订单号
if ("TRADE_FINISHED".equals(tradeStatus) || "TRADE_SUCCESS".equals(tradeStatus)) {
//实际业务代码
}
}
return "fail";
}
public boolean rsaCheckV1(HttpServletRequest request) {
try {
//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext ();) {
String name = ( String )iter.next();
String[] values = (String[])requestParams.get(name);
String valueStr="";
for(int i = 0;i < values.length; i++){
valueStr = (i== values.length-1)?valueStr+values[i]:valueStr+values[i] + ",";
}
//乱码解决,这段代码在出现乱码时使用。
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name,valueStr);
}
//切记alipayPublicCertPath是应用公钥证书路径,请去open.alipay.com对应应用下载。
boolean flag = AlipaySignature.rsaCertCheckV1(params,alipayConfig.getPayPath()+"alipayCertPublicKey_RSA2.crt",alipayConfig.getCharset(),"RSA2");
return flag;
} catch (AlipayApiException e) {
System.out.println(e);
return false;
}
}
异步通知
1.支付宝规定异步通知接口必须是外网可访问切不能在路径中携带参数
2.我在本地测试中使用了内网穿透
3.打开内网穿透后 将异步通知地址配置在支付宝开放平台上
微信支付
微信支付是收费功能
1 准备工作
微信支付功能开通文档 – 微信开放平台文档
微信开放平台文档接入准备 完成apiv3 key , api证书的设置
创建完成app后
获取支付能力
获取完成后查看开发信息
填写app包名
应用签名
安装到有app的手机上输入包名点击生成,生成后复制填写到微信开放平台的应用签名
设置apiv3key 和证书
2代码
依赖
<!-- 微信支付sdk -->
<dependency>
<groupId>com.github.wechatpay-apiv3</groupId>
<artifactId>wechatpay-apache-httpclient</artifactId>
<version>0.3.0</version>
</dependency>
微信支付配置类
public String APP_ID = "1"; //应用id
public String KEY = ""; //apiv3 密码
public String MCH_ID = ""; //商户id
public String merchantSerialNumber = "";//证书编号
@Value("${file.path.PayPath}")
private String payPath; //证书地址
private PrivateKey PRIVATE_KEY;
public String path = "https://api.mch.weixin.qq.com/v3/pay/transactions/app";//app支付统一下单地址
public String schema = "WECHATPAY2-SHA256-RSA2048";
@Bean
public CloseableHttpClient httpClient() throws IOException {
//读取私匙证书
PrivateKey private_key = this.getPRIVATE_KEY();
//定时更新证书
ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
new WechatPay2Credentials(MCH_ID, new PrivateKeySigner(merchantSerialNumber, private_key)),
KEY.getBytes(StandardCharsets.UTF_8));
//请求构建器
WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
.withMerchant(MCH_ID, merchantSerialNumber, PRIVATE_KEY)
.withValidator(new WechatPay2Validator(verifier));
//http请求
return builder.build();
}
public PrivateKey getPRIVATE_KEY() {
try {
PRIVATE_KEY = PemUtil.loadPrivateKey(new FileInputStream(payPath + "apiclient_key.pem"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return this.PRIVATE_KEY;
}
微信支付控制层
因为微信支付代码比较复杂所以我就写在了service层中
@Autowired
private PayService payServiceImpl;
/**
* 微信支付
* @param userId 用户id
* @param paymentType 付款方式
* @return
* @throws AlipayApiException
*/
@GetMapping("/wxpay")
public Object wxpay(String userId, String paymentType, String orderInfo) {
return payServiceImpl.wxPay("/pay/wxpay/notify");
}
/**
* 支付异步通知
* 接收到异步通知并验签通过后,一定要检查通知内容,
*/
@PostMapping("/wxpay/notify")
public String wxnotify(HttpServletRequest request){
//参数解析
Map<String, String> prem = payServiceImpl.wxNotify(request);
//是否支付成功
if (prem.get("trade_state").equals("SUCCESS")&&prem.get("trade_state_desc").equals("支付成功")){
业务代码
}
return "fail";
}
微信支付服务层
@Autowired
private WxPay wxPay;
public Object wxPay(String notulr) {
//生成32位随机字母加数字 并转换为大写
String noncestr = RandomStringUtils.randomAlphanumeric(32).toLowerCase().toUpperCase();
JSONObject jsonObject =new JSONObject();
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
httpPost.addHeader("Accept", "application/json");
httpPost.addHeader("Content-type","application/json; charset=utf-8");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode.put("appid", wxPay.APP_ID)//appID
.put("mchid", wxPay.MCH_ID)//商户ID
.put("description", "商品描述")
.put("notify_url", notulr)//异步通知接口
.put("out_trade_no", "11020210100101");//商户订单ID
rootNode.putObject("amount")
.put("total", 888800);//付款金额
//请求头
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
//请求返回结构
String bodyAsString = EntityUtils.toString(response.getEntity());
//将返回结果转换为jsonObject
jsonObject=JSONObject.parseObject(bodyAsString);
} catch (IOException e) {
e.printStackTrace();
}
//当返回结果中有prepay_id这个key时 预付订单成功
if (jsonObject.containsKey("prepay_id")){//预订单成功
//返回结果参数
jsonObject.put("appid", wxPay.APP_ID);
jsonObject.put("package","Sign=WXPay");//固定值
jsonObject.put("partnerid", wxPay.MCH_ID);//微信支付商户号
jsonObject.put("timestamp",vipOrder.getOrderTime().getTime());//时间戳
jsonObject.put("noncestr", noncestr);
jsonObject.put("key", wxPay.KEY);
//因为是基于uni-app编写的app 所以直接在后台进行唤起支付加密
List<String> list=new ArrayList<>();
list.add(wxPay.APP_ID);
list.add(String.valueOf(vipOrder.getOrderTime().getTime()));
list.add(noncestr);
list.add((String) jsonObject.get("prepay_id"));
jsonObject.put("sign",this.createQian(list));// app唤起签名
return jsonObject;
}
return "error";
}
/**
* 生成app唤起支付签名
* @param pram 参数
* 应用id
* 时间戳
* 随机字符串
* 预支付交易会话ID
* @return
*/
public String createQian(List<String> pram) {
StringBuffer sBuffer = new StringBuffer();
for (String str : pram) {
sBuffer.append(str+"\n");
}
//使用私匙文件对字符串进行 WECHATPAY2-SHA256-RSA2048
//对结果进行base64加密
Signature sign = null;
byte[] sign1=null;
try {
sign = Signature.getInstance("SHA256withRSA");
sign.initSign(wxPay.getPRIVATE_KEY());
sign.update(sBuffer.toString().getBytes("UTF-8"));
sign1 = sign.sign();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException | SignatureException | InvalidKeyException e) {
e.printStackTrace();
}
return Base64.getEncoder().encodeToString(sign1);
}
//解密返回参数
public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
throws GeneralSecurityException, IOException {
try {
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
SecretKeySpec key = new SecretKeySpec(wxPay.KEY.getBytes(), "AES");
GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
cipher.init(Cipher.DECRYPT_MODE, key, spec);
cipher.updateAAD(associatedData);
return new String(cipher.doFinal(Base64.getDecoder().decode(ciphertext)), "utf-8");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalStateException(e);
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
throw new IllegalArgumentException(e);
}
}
文档地址
其他
微信支付在开放平台上不修改应用签名是可以唤起支付的但是只能唤起一次,第二次会提示应用签名错误
微信支付异步通知可以使用http 微信的官方文档上说必须使用HTTPS 但是HTTPS并不好进行调试部署
微信支付异步通知的时间是 (通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m)
支付宝异步通知地址只要是外网可访问即可
支付宝异步通知的时间是 4m,10m,10m,1h,2h,6h,15h