目录
基本概述
支付接入
支付宝
APP支付
移动端网页支付
PC端支付
微信
APP支付
移动端网页支付
小程序支付
PC端网页支付
PayPal(代码已完成,流程待补充)
订单支付
创建订阅(订阅支付)
我把做过的第三方支付封装成“拿来即用”的Demo。
文章内容尽量不讲废话,只讲“是什么”,不讲“为什么”。
包括:
支付宝支付:[APP支付]、[移动端网页支付]、[PC端支付]
微 信 支 付 :[APP支付]、[移动端网页支付]、[PC端网页支付]、[小程序支付]
PayPal支付:[订单支付] 、[订阅支付]
代码地址(重点):https://github.com/scorpion-lailai/pay-sdk-demo.git
支付逻辑不懂的看这里:聚合支付、单商户多商户支付、微信/支付宝/PayPal支付流程、支付政策法规_「蝎子莱莱」的博客-CSDN博客聚合支付、单商户和多商户支付、[支付宝/微信]APP支付、[支付宝/微信]H5支付、[微信]PC支付、[支付宝]PC支付、[微信]小程序支付、PayPal支付、支付政策法规https://blog.csdn.net/qq_39906413/article/details/127802966?spm=1001.2014.3001.5502
需要的配置信息: pay.config.alipay.appid=支付宝APPid pay.config.alipay.private.key=你的privateKey pay.config.alipay.public.key=你的publicKey pay.config.alipay.sign.type=RSA2
截图
流程:
1. 填写[订单号]和[订单金额],请求接口
2.返回客户端支付调用需要的参数,客户端拿到参数后,可以直接调起支付
后端参照及接口说明:app支付接口2.0 | 网页&移动应用
代码:
public static String APP(Long orderId, BigDecimal amount, NotifyUrl notifyUrl) {
// 设置请求的参数
JSONObject json = new JSONObject();
json.put("out_trade_no", orderId.toString());
json.put("total_amount", amount.toString());
json.put("subject", "APP在线支付");
json.put("product_code", "QUICK_MSECURITY_PAY");
AlipayClient alipayClient = AliPaySingleTransfer.getAlipayClient();
AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();
// 回调服务端URL
alipayRequest.setNotifyUrl(CURRENT_ENV_URL + notifyUrl.getUrl());
alipayRequest.setBizContent(json.toJSONString());
// 请求
String result;
try {
result = alipayClient.sdkExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
log.error(e.getMessage(), e);
throw new ResultException(Errors.ERROR);
}
return result;
}
截图
1. 填写[订单号]和[订单金额]和[支付成功后前端页面跳转地址],请求接口
2.返回客户端信息为[form表单]
3. 客户端把[form表单]放到页面上后,再提交表单,即可跳转支付页面
后端参照及接口说明:手机网站支付接口2.0 | 网页&移动应用
代码:
public static String H5(Long orderId, BigDecimal amount, String returnUrl, NotifyUrl notifyUrl) {
// 设置请求的参数
JSONObject json = new JSONObject();
json.put("out_trade_no", orderId.toString());
json.put("total_amount", amount.toString());
json.put("subject", "移动H5在线支付");
json.put("product_code", "QUICK_WAP_WAY");
AlipayClient alipayClient = AliPaySingleTransfer.getAlipayClient();
AlipayTradeWapPayRequest wap = new AlipayTradeWapPayRequest();
// 回调服务端URL
wap.setReturnUrl(returnUrl);
wap.setNotifyUrl(CURRENT_ENV_URL + notifyUrl.getUrl());
wap.setBizContent(json.toJSONString());
// 请求
String result;
try {
result = alipayClient.pageExecute(wap).getBody();
} catch (AlipayApiException e) {
log.error(e.getMessage(), e);
throw new ResultException(Errors.ERROR);
}
return result;
}
截图
流程:
1. 填写[订单号]和[订单金额]和[支付成功后前端页面跳转地址],请求接口
2.返回客户端信息为[form表单]
3. 客户端把[form表单]放到页面上后,再提交表单,即可跳转支付页面
代码:
public static String PC(Long orderId, BigDecimal amount, String returnUrl, NotifyUrl notifyUrl) {
// 设置请求的参数
JSONObject json = new JSONObject();
json.put("out_trade_no", orderId);
json.put("total_amount", amount);
json.put("subject", "PC端在线支付");
json.put("body", "无");
json.put("product_code", "FAST_INSTANT_TRADE_PAY");
AlipayClient alipayClient = AliPaySingleTransfer.getAlipayClient();
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
// 支付成功后,页面跳转
alipayRequest.setReturnUrl(returnUrl);
// 回调服务端URL
alipayRequest.setNotifyUrl(CURRENT_ENV_URL + notifyUrl.getUrl());
alipayRequest.setBizContent(json.toJSONString());
// 请求
String result;
try {
result = alipayClient.pageExecute(alipayRequest).getBody();
} catch (AlipayApiException e) {
log.error(e.getMessage(), e);
throw new ResultException(Errors.ERROR);
}
return result;
}
需要的配置信息: #商户号 pay.config.wechat.mchid=你的商户号 #商户私钥文件 pay.config.wechat.privateKey.path=商户私钥文件路径 #平台证书 pay.config.wechat.cert.path=平台证书文件路径 #商户平台的v3Key pay.config.wechat.v3Key=v3Key #证书序列号 pay.config.wechat.serialNo=序列号 #app支付的AppId pay.config.wechat.app.appid=AppId #pc支付的AppId pay.config.wechat.pc.appid=AppId #移动端网页支付的AppId pay.config.wechat.h5.appid=AppId #小程序支付的AppId pay.config.wechat.miniProgram.appid=AppId
[平台证书]生成方式: https://github.com/wechatpay-apiv3/CertificateDownloader
截图
流程:
1. 填写[订单号]和[订单金额],请求接口
2.返回客户端支付调用需要的参数,客户端拿到参数后,可以直接调起支付
后端参照及接口说明:微信支付-开发者文档
前端参照及接口说明:微信支付-开发者文档
代码:
public static Map APP(Long orderId, BigDecimal amount, NotifyUrl notifyUrl) {
String APPID = APPID_APP;
CloseableHttpClient httpClient = buildHttp();
HttpPost httpPost = buildHttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/app");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode
.put("mchid", mchId)
.put("appid", APPID)
.put("description", "APP在线付费")
.put("notify_url", CURRENT_ENV_URL + notifyUrl.getUrl())
.put("out_trade_no", orderId.toString());
amount = (amount.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP));
rootNode.putObject("amount").put("total", amount.intValue());
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
// http repsonse
HttpEntity entity = response.getEntity();
String s = EntityUtils.toString(entity);
log.info("返回参数为:{}", s);
JSONObject json = JSON.parseObject(s);
String prepayId = json.getString("prepay_id");
// 拼接参数返回APP
String nonce = generateNonceStr();
long timeStamp = System.currentTimeMillis() / 1000;
Map result = new HashMap<>();
result.put("appId", APPID); // 应用appid
result.put("nonceStr", nonce);
result.put("timestamp", Long.toString(timeStamp));
result.put("prepayId", prepayId); // 返回App
String sign =
PrivateKeySign.getToken(APPID, prepayId, nonce, timeStamp, getPrivateKey(privateKeyPath));
// 签名
result.put("sign", sign); // 再次签名
result.put("package", "Sign=WXPay");
result.put("partnerId", mchId); // 商户号
return result;
} catch (Exception e) {
log.error(e.toString(), e);
throw new ResultException(Errors.ERROR);
}
}
截图
流程:
1. 填写[订单号]和[订单金额],请求接口
2.返回客户端一个url链接,客户端打开链接后,可以直接调起支付
后端参照及接口说明:微信支付-开发者文档
前端参照及接口说明:微信支付-开发者文档
public static Map H5(Long orderId, BigDecimal amount, NotifyUrl notifyUrl) {
CloseableHttpClient httpClient = buildHttp();
HttpPost httpPost = buildHttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/h5");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode
.put("mchid", mchId)
.put("appid", APPID_H5)
.put("description", "移动H5在线付费")
.put("notify_url", CURRENT_ENV_URL + notifyUrl.getUrl())
.put("out_trade_no", orderId.toString());
amount = (amount.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP));
rootNode.putObject("amount").put("total", amount.intValue());
ObjectNode sceneInfo = rootNode.putObject("scene_info");
sceneInfo.put("payer_client_ip", IPUtils.getIpAddress());
ObjectNode h5InfoNode = sceneInfo.putObject("h5_info");
h5InfoNode.put("type", "Wap");
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
// http repsonse
HttpEntity entity = response.getEntity();
String s = EntityUtils.toString(entity);
log.info("返回参数为:{}", s);
JSONObject json = JSON.parseObject(s);
String h5Url = json.getString("h5_url");
// 拼接参数返回APP
Map result = new HashMap<>();
result.put("h5_url", h5Url); // 应用appid
return result;
} catch (Exception e) {
log.error(e.toString(), e);
throw new ResultException(Errors.ERROR);
}
}
流程:
1. 填写[订单号]和[订单金额]和[openId],请求接口
2.返回客户端[prepay_id],客户端拿到prepay_id就能调起支付
后端参照及接口说明:微信支付-开发者文档
前端参照及接口说明:微信支付-开发者文档
public static Map MiniProgram(
Long orderId, BigDecimal amount, NotifyUrl notifyUrl, String openId) {
String APPID = APPID_MiniProgram;
CloseableHttpClient httpClient = buildHttp();
HttpPost httpPost = buildHttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
rootNode
.put("mchid", mchId)
.put("appid", APPID)
.put("description", "小程序在线付费")
.put("notify_url", CURRENT_ENV_URL + notifyUrl.getUrl())
.put("out_trade_no", orderId.toString());
amount = (amount.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP));
rootNode.putObject("amount").put("total", amount.intValue());
rootNode.putObject("payer").put("openid", openId);
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
// http repsonse
HttpEntity entity = response.getEntity();
String s = EntityUtils.toString(entity);
log.info("返回参数为:{}", s);
JSONObject json = JSON.parseObject(s);
String prepayId = json.getString("prepay_id");
String packageStr = "prepay_id=" + prepayId;
// 拼接参数返回APP
String nonce = generateNonceStr();
long timeStamp = System.currentTimeMillis() / 1000;
Map result = new HashMap<>();
result.put("appId", APPID); // 应用appid
result.put("timeStamp", Long.toString(timeStamp));
result.put("nonceStr", nonce);
result.put("package", packageStr);
String sign =
PrivateKeySign.getToken(
APPID, packageStr, nonce, timeStamp, getPrivateKey(privateKeyPath));
// 签名
result.put("paySign", sign); // 再次签名
result.put("signType", "RSA"); // 再次签名
return result;
} catch (Exception e) {
log.error(e.toString(), e);
throw new ResultException(Errors.ERROR);
}
}
截图
流程:
1. 填写[订单号]和[订单金额],请求接口
2.返回客户端[二维码链接],客户端拿到[二维码链接]后,生成二维码。
3.用户使用手机微信扫描,即可支付。
后端参照及接口说明:微信支付-开发者文档
前端参照及接口说明:微信支付-开发者文档
代码:
public static Map PC(Long orderId, BigDecimal amount, NotifyUrl notifyUrl) {
CloseableHttpClient httpClient = buildHttp();
HttpPost httpPost = buildHttpPost("https://api.mch.weixin.qq.com/v3/pay/transactions/native");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode rootNode = objectMapper.createObjectNode();
// 65秒后过期
Date date = DateUtil.getAfterDate(new Date(), Calendar.SECOND, 65);
String formatDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(date);
log.info(formatDate);
rootNode
.put("mchid", mchId)
.put("appid", APPID_PC)
.put("description", "PC端在线付费")
.put("notify_url", CURRENT_ENV_URL + notifyUrl.getUrl())
.put("out_trade_no", orderId.toString())
.put("time_expire", formatDate);
amount = (amount.multiply(new BigDecimal(100)).setScale(0, RoundingMode.HALF_UP));
rootNode.putObject("amount").put("total", amount.intValue());
try {
objectMapper.writeValue(bos, rootNode);
httpPost.setEntity(new StringEntity(bos.toString("UTF-8"), "UTF-8"));
CloseableHttpResponse response = httpClient.execute(httpPost);
// http repsonse
HttpEntity entity = response.getEntity();
String s = EntityUtils.toString(entity);
log.info("返回参数为:{}", s);
JSONObject json = JSON.parseObject(s);
String codeUrl = json.getString("code_url");
// 拼接参数返回APP
Map result = new HashMap<>();
result.put("codeUrl", codeUrl); // 应用appid
return result;
} catch (Exception e) {
log.error(e.toString(), e);
throw new ResultException(Errors.ERROR);
}
}
需要的配置信息: #paypay的ClientId和Secret pay.config.paypal.key=key pay.config.paypal.secret=secret #paypalModel 可选参数:[sandbox/production] pay.config.paypal.mode=sandbox #沙盒 https://api-m.sandbox.paypal.com #正式 https://api-m.paypal.com pay.config.paypal.base.url=https://api-m.sandbox.paypal.com #PAYPAL支付成功后,前端跳转的页面 pay.config.paypal.return.url=https://www.baidu.com/s?wd=支付成功 #PAYPAL支付取消后,前端的跳转页面 pay.config.paypal.cancel.url=https://www.baidu.com/s?wd=支付取消
如果PayPal需要接订阅(比如按[月/年]自动扣费),还需要创建[商品]和[付款计划]。
PayPal获取Token
代码:
public String getPaypalToken() {
String body = HttpRequest.post(PAYPAL_BASE_URL + "/v1/oauth2/token")
.basicAuth(PAYPAL_KEY, PAYPAL_SECRET)
.form("grant_type", "client_credentials")
.execute().body();
log.info("[paypal支付]-[获取token]->{}", body);
JSONObject jsonObject = JSONObject.parseObject(body);
return jsonObject.get("access_token").toString();
}
代码
public Object paypalOrderPay(BigDecimal subtotal, Long orderId) {
// 接口请参照:https://developer.paypal.com/api/rest/
/*
* 支付流程:
* 1. 创建Payer对象并设置PaymentMethod
* 2. 设置RedirectUrls并设置cancelURL和returnURL
* 3. 设置详细信息并添加PaymentDetails
* 4. 设置金额
* 5. 设置交易
* 6. 添加付款明细并将Intent设置为“授权”
* 7. 通过传递clientID、secret和mode创建APIContext
* 8. 创建Payment对象并获取paymentID
* 9. 将payerID 设置为 PaymentExecution 对象
* 10.执行支付并获得授权
*/
BigDecimal shipping = new BigDecimal("0.00");
BigDecimal tax = new BigDecimal("0.00");
BigDecimal total = shipping.add(subtotal).add(tax);
Payer payer = new Payer();
payer.setPaymentMethod("paypal");
// 重定向网址
RedirectUrls redirectUrls = new RedirectUrls();
//取消支付url
redirectUrls.setCancelUrl(PAYPAL_CANCEL_URL);
//支付成功后,前端跳转页面
redirectUrls.setReturnUrl(PAYPAL_RETURN_URL);
// 设置交易金额 1.运费 2.小计 3.税
Details details = new Details();
details.setShipping(shipping.toString());
details.setSubtotal(subtotal.toString());
details.setTax(tax.toString());
// 设置付款金额 total = shipping + subtotal + tax
Amount amount = new Amount();
amount.setCurrency("USD");
amount.setTotal(total.toString());
amount.setDetails(details);
// 设置交易信息
Transaction transaction = new Transaction();
transaction.setAmount(amount);
transaction.setDescription("desc");
transaction.setCustom(orderId.toString());
List transactions = new ArrayList<>();
transactions.add(transaction);
// 添加交易明细
Payment payment = new Payment();
//设置付款意图以授权
payment.setIntent("authorize");
payment.setPayer(payer);
payment.setTransactions(transactions);
payment.setRedirectUrls(redirectUrls);
//传递 clientID、secret 和 mode。 最简单、使用最广泛的选项。
APIContext apiContext = new APIContext(PAYPAL_KEY, PAYPAL_SECRET, PAYPAL_MODE);
try {
payment = payment.create(apiContext);
log.info("[paypal支付]-[生成订单]->{}", payment);
} catch (PayPalRESTException e) {
log.error("paypal支付异常", e);
throw new ResultException(Errors.ERROR);
}
List links = payment.getLinks();
return getRelLink(links, "approval_url").getHref();
}
代码:
public Object createSubscription(String planId, Long orderId) {
Map map = new HashMap<>(4);
map.put("Content-Type", "application/json");
map.put("Authorization", getPaypalToken());
String string = handlerSubsParam(planId, orderId);
String body = HttpRequest.post(PAYPAL_BASE_URL + "/v1/billing/subscriptions")
.addHeaders(map)
.basicAuth(PAYPAL_KEY, PAYPAL_SECRET)
.body(string)
.execute().body();
log.info("[paypal支付]-[创建订阅]->{}", body);
JSONObject jsonObject = JSONObject.parseObject(body);
List links = JSONObject.parseArray(jsonObject.get("links").toString(), Links.class);
return getRelLink(links, "approve").getHref();
}
/**
* 处理订阅参数
*/
private String handlerSubsParam(String planId, Long orderId) {
SubscriptionDTO subscriptionDTO = new SubscriptionDTO();
subscriptionDTO.setPlanId(planId);
subscriptionDTO.setCustomId(orderId.toString());
ApplicationContext applicationContext = new ApplicationContext();
applicationContext.setCancelUrl(PAYPAL_CANCEL_URL);
applicationContext.setReturnUrl(PAYPAL_RETURN_URL);
applicationContext.setPaymentMethod(new PaymentMethod());
subscriptionDTO.setApplicationContext(applicationContext);
String string;
try {
string = new ObjectMapper().writeValueAsString(subscriptionDTO);
} catch (Exception e) {
log.error("创建订阅失败", e);
throw new ResultException(Errors.ERROR);
}
return string;
}