From 2fa018a0c9dce93289d54bcdd15bf197bb4e450d Mon Sep 17 00:00:00 2001 From: xiafang Date: Tue, 14 Feb 2023 17:44:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 270 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 268 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8a53eea..d32f1d0 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,6 @@ Java微信支付V3支付Spring Boot Starter,支持微信优惠券,代金券 ## 文档地址 - [payment-spring-boot GitHub文档](https://notfound403.github.io/payment-spring-boot) -- [payment-spring-boot Gitee文档](https://felord.gitee.io/payment-spring-boot) - ## 目前已经实现所有服务商和直连商户接口 @@ -94,6 +92,274 @@ Java微信支付V3支付Spring Boot Starter,支持微信优惠券,代金券 > 随着版本迭代功能会增加,可通过API注册表类`WechatPayV3Type`进行API接口检索。 +## 使用入门 +### 集成配置 +关于集成配置请详细阅读[payment-spring-boot GitHub文档](https://notfound403.github.io/payment-spring-boot)中[快速接入](https://notfound403.github.io/payment-spring-boot/#/quick_start)章节 +### 调用示例 +#### 开启支付 +需要手动通过`@EnableMobilePay`注解开启支付 +```java +import cn.felord.payment.autoconfigure.EnableMobilePay; +import org.springframework.context.annotation.Configuration; + +@EnableMobilePay +@Configuration +public class PayConfig { +} +``` + +#### 支付接口调用 +这里简单以小程序支付为例,写了一个Spring MVC 控制器,在实践中建议对`WechatApiProvider`进行二次封装作服务层调用 +```java +import cn.felord.payment.wechat.enumeration.TradeBillType; +import cn.felord.payment.wechat.v3.WechatApiProvider; +import cn.felord.payment.wechat.v3.WechatDirectPayApi; +import cn.felord.payment.wechat.v3.model.*; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.core.io.Resource; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDate; +import java.time.Month; + +/** + * 支付接口开发样例,以小程序支付为例. + */ +@Profile({"wechat", "dev"}) +@RestController +@RequestMapping("/marketing") +public class PayController { + @Autowired + private WechatApiProvider wechatApiProvider; + String TENANT_ID = "mobile"; + /** + * 总流程建议为 生成商品订单 -> 生成对应的支付订单 -> 支付操作 -> 支付结果回调更新 -> 结束 + *

+ * 此处建议在商品订单生成之后调用 + * + * @param orderId 商品订单id + * @return the object node + */ + @PostMapping("/js") + public ObjectNode js(@RequestParam String orderId) { + + //TODO + // 查询该orderId下是否生成了支付订单 + // 如果没有 + // 新增支付订单存入数据库 并标明支付状态为【待支付】 + // 根据新生成的支付订单信息向微信支付发起支付 并根据返回结果进行处理 + // 如果有状态为待支付 + // 根据待支付订单信息向微信支付发起支付 并根据返回结果进行处理 + // 如果有状态为待支付之外的状态 + // 根据产品的业务设计自行实现 + // 支付状态更新逻辑在【回调接口 /wxpay/callbacks/transaction】中处理 需要幂等处理 + + // 开发时需要指定使用的商户租户配置 这里为 mobile 请参考 application-wechat.yml + + + PayParams payParams = new PayParams(); + + payParams.setDescription("felord.cn"); + // + // 商户侧唯一订单号 建议为商户侧支付订单号 订单表主键 或者唯一标识字段 + payParams.setOutTradeNo("X135423420201521613448"); + // 需要定义回调通知 + payParams.setNotifyUrl("/wxpay/callbacks/transaction"); + Amount amount = new Amount(); + amount.setTotal(100); + payParams.setAmount(amount); + // 此类支付 Payer 必传 且openid需要同appid有绑定关系 具体去看文档 + Payer payer = new Payer(); + payer.setOpenid("ooadI5kQYrrCqpgbisvC8bEw_oUc"); + payParams.setPayer(payer); + + return wechatApiProvider.directPayApi(TENANT_ID) + .jsPay(payParams) + .getBody(); + } + + + /** + * 下载对账单 如果要解析内容的话自行实现 + * + * @return the response entity + */ + @GetMapping("/tradebill") + public ResponseEntity download() { + WechatDirectPayApi wechatDirectPayApi = wechatApiProvider.directPayApi(TENANT_ID); + + TradeBillParams tradeBillParams = new TradeBillParams(); + tradeBillParams.setBillDate(LocalDate.of(2021, Month.MAY, 20)); + tradeBillParams.setBillType(TradeBillType.ALL); + return wechatDirectPayApi.downloadTradeBill(tradeBillParams); + } + + /** + * 下载申请资金账单 如果要解析内容的话自行实现 + * + * @return the response entity + */ + @GetMapping("/fundflowbill") + public ResponseEntity fundFlowBill() { + WechatDirectPayApi wechatDirectPayApi = wechatApiProvider.directPayApi(TENANT_ID); + + FundFlowBillParams fundFlowBillParams = new FundFlowBillParams(); + fundFlowBillParams.setBillDate(LocalDate.of(2021, Month.MAY, 20)); + + return wechatDirectPayApi.downloadFundFlowBill(fundFlowBillParams); + } +} +``` +#### 回调示例 +回调可通过以下示例实现,多租户的回调可将租户ID`tenantId`作为路径参数来实现 + +```java +import cn.felord.payment.wechat.v3.WechatApiProvider; +import cn.felord.payment.wechat.v3.WechatMarketingFavorApi; +import cn.felord.payment.wechat.v3.WechatPayCallback; +import cn.felord.payment.wechat.v3.model.ResponseSignVerifyParams; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 注意为了演示该配置在使用微信配置application-wechat.yaml才生效 + *

+ * 务必保证回调接口的幂等性 + *

+ * 微信回调控制器,当支付成功、代金券核销成功后,微信支付服务器会通过回调进行通知商户侧。 + * 商户侧可以根据微信的回调通知进行支付的后续处理,例如支付状态的变更等等。 + * 需要注意的是回调接口需要白名单放行。 + *

+ * 开发者只需要编写对结果的{@link java.util.function.Consumer}即可。 + *

+ * 请注意:返回的格格式必须是{@link WechatPayCallback} 给出的格式,不能被包装和更改,切记! + * @author felord.cn + * @since 1.0.0.RELEASE + */ +@Profile({"wechat","dev"}) +@RestController +@RequestMapping("/wxpay/callbacks") +public class CallbackController { + private static final String TENANT_ID = "mobile"; + @Autowired + private WechatApiProvider wechatApiProvider; + + + /** + * 代金券核销通知. + *

+ * 需要手动调用{@link WechatMarketingFavorApi#setMarketingFavorCallback(String)} 设置,一次性操作! + * + * @param wechatpaySerial the wechatpay serial + * @param wechatpaySignature the wechatpay signature + * @param wechatpayTimestamp the wechatpay timestamp + * @param wechatpayNonce the wechatpay nonce + * @param request the request + * @return the map + */ + @SneakyThrows + @PostMapping("/coupon") + public Map couponCallback( + @RequestHeader("Wechatpay-Serial") String wechatpaySerial, + @RequestHeader("Wechatpay-Signature") String wechatpaySignature, + @RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp, + @RequestHeader("Wechatpay-Nonce") String wechatpayNonce, + HttpServletRequest request) { + String body = request.getReader().lines().collect(Collectors.joining()); + // 对请求头进行验签 以确保是微信服务器的调用 + ResponseSignVerifyParams params = new ResponseSignVerifyParams(); + params.setWechatpaySerial(wechatpaySerial); + params.setWechatpaySignature(wechatpaySignature); + params.setWechatpayTimestamp(wechatpayTimestamp); + params.setWechatpayNonce(wechatpayNonce); + params.setBody(body); + return wechatApiProvider.callback(TENANT_ID).couponCallback(params, data -> { + //TODO 对回调解析的结果进行消费 需要保证消费的幂等性 微信有可能多次调用此接口 + }); + } + + /** + * 微信支付成功回调. + *

+ * 无需开发者判断,只有扣款成功微信才会回调此接口 + * + * @param wechatpaySerial the wechatpay serial + * @param wechatpaySignature the wechatpay signature + * @param wechatpayTimestamp the wechatpay timestamp + * @param wechatpayNonce the wechatpay nonce + * @param request the request + * @return the map + */ + @SneakyThrows + @PostMapping("/transaction") + public Map transactionCallback( + @RequestHeader("Wechatpay-Serial") String wechatpaySerial, + @RequestHeader("Wechatpay-Signature") String wechatpaySignature, + @RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp, + @RequestHeader("Wechatpay-Nonce") String wechatpayNonce, + HttpServletRequest request) { + String body = request.getReader().lines().collect(Collectors.joining()); + // 对请求头进行验签 以确保是微信服务器的调用 + ResponseSignVerifyParams params = new ResponseSignVerifyParams(); + params.setWechatpaySerial(wechatpaySerial); + params.setWechatpaySignature(wechatpaySignature); + params.setWechatpayTimestamp(wechatpayTimestamp); + params.setWechatpayNonce(wechatpayNonce); + params.setBody(body); + return wechatApiProvider.callback(TENANT_ID).transactionCallback(params, data -> { + //TODO 对回调解析的结果进行消费 需要保证消费的幂等性 微信有可能多次调用此接口 + }); + } + + /** + * 微信合单支付成功回调. + *

+ * 无需开发者判断,只有扣款成功微信才会回调此接口 + * + * @param wechatpaySerial the wechatpay serial + * @param wechatpaySignature the wechatpay signature + * @param wechatpayTimestamp the wechatpay timestamp + * @param wechatpayNonce the wechatpay nonce + * @param request the request + * @return the map + */ + @SneakyThrows + @PostMapping("/combine_transaction") + public Map combineTransactionCallback( + @RequestHeader("Wechatpay-Serial") String wechatpaySerial, + @RequestHeader("Wechatpay-Signature") String wechatpaySignature, + @RequestHeader("Wechatpay-Timestamp") String wechatpayTimestamp, + @RequestHeader("Wechatpay-Nonce") String wechatpayNonce, + HttpServletRequest request) { + String body = request.getReader().lines().collect(Collectors.joining()); + // 对请求头进行验签 以确保是微信服务器的调用 + ResponseSignVerifyParams params = new ResponseSignVerifyParams(); + params.setWechatpaySerial(wechatpaySerial); + params.setWechatpaySignature(wechatpaySignature); + params.setWechatpayTimestamp(wechatpayTimestamp); + params.setWechatpayNonce(wechatpayNonce); + params.setBody(body); + return wechatApiProvider.callback(TENANT_ID).combineTransactionCallback(params, data -> { + //TODO 对回调解析的结果进行消费 需要保证消费的幂等性 微信有可能多次调用此接口 + }); + } +} +``` + + + ## 开源协议 **Apache 2.0**