diff --git a/basic-properties/src/main/java/ink/wgink/properties/wechat/pay/v3/PayProperties.java b/basic-properties/src/main/java/ink/wgink/properties/wechat/pay/v3/PayProperties.java new file mode 100644 index 00000000..5c1cd4a8 --- /dev/null +++ b/basic-properties/src/main/java/ink/wgink/properties/wechat/pay/v3/PayProperties.java @@ -0,0 +1,53 @@ +package ink.wgink.properties.wechat.pay.v3; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @ClassName: PayProperties + * @Description: 支付配置 + * @Author: wanggeng + * @Date: 2021/8/19 4:05 下午 + * @Version: 1.0 + */ +@Configuration +@ConfigurationProperties(prefix = "open-platform.wechat.pay") +public class PayProperties { + + private Boolean active; + private String mchid; + private String certificatePath; + private String keyPath; + + public Boolean getActive() { + return active; + } + + public void setActive(Boolean active) { + this.active = active; + } + + public String getMchid() { + return mchid == null ? "" : mchid.trim(); + } + + public void setMchid(String mchid) { + this.mchid = mchid; + } + + public String getCertificatePath() { + return certificatePath == null ? "" : certificatePath.trim(); + } + + public void setCertificatePath(String certificatePath) { + this.certificatePath = certificatePath; + } + + public String getKeyPath() { + return keyPath == null ? "" : keyPath.trim(); + } + + public void setKeyPath(String keyPath) { + this.keyPath = keyPath; + } +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayAuthorizationTypeEnum.java b/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayAuthorizationTypeEnum.java new file mode 100644 index 00000000..6f4401ab --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayAuthorizationTypeEnum.java @@ -0,0 +1,23 @@ +package ink.wgink.module.wechat.enums; + +/** + * @ClassName: AuthorizationTypeEnum + * @Description: 认证类型 + * @Author: wanggeng + * @Date: 2021/8/19 5:17 下午 + * @Version: 1.0 + */ +public enum PayAuthorizationTypeEnum { + + WECHATPAY2_SHA256_RSA2048("WECHATPAY2-SHA256-RSA2048"); + + private String value; + + PayAuthorizationTypeEnum(String value) { + this.value = value; + } + + public String getValue() { + return value == null ? "" : value.trim(); + } +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayErrorMsgEnum.java b/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayErrorMsgEnum.java index 4a76f5a3..e872898c 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayErrorMsgEnum.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/enums/PayErrorMsgEnum.java @@ -9,8 +9,14 @@ package ink.wgink.module.wechat.enums; */ public enum PayErrorMsgEnum { + /** + * 202 错误 + */ USERPAYING_200(202, "USERPAYING", "用户支付中,需要输入密码", "等待5秒,然后调用被扫订单结果查询API,查询当前订单的不同状态,决定下一步的操作"), + /** + * 400 错误 + */ APPID_MCHID_NOT_MATCH_400(400, "APPID_MCHID_NOT_MATCH", "appid和mch_id不匹配", "请确认appid和mch_id是否匹配"), PARAM_ERROR_400(400, "PARAM_ERROR", "业务规则限制", "请根据接口返回的详细信息检查请求参数"), ORDER_CLOSED(400, "ORDER_CLOSED", "订单已关闭", "当前订单已关闭,请重新下单"), @@ -29,8 +35,14 @@ public enum PayErrorMsgEnum { ORDERPAID_400(400, "ORDERPAID", "订单已支付", "请确认该订单号是否重复支付,如果是新单,请使用新订单号提交"), ORDERREVERSED_400(400, "ORDERREVERSED", "订单已撤销", "当前订单状态为“订单已撤销”,请提示用户重新支付"), + /** + * 401 错误 + */ SIGN_ERROR_401(401, "SIGN_ERROR", "签名错误", "请检查签名参数和方法是否都符合签名算法要求"), + /** + * 403 错误 + */ TRADE_ERROR_403(403, "TRADE_ERROR", "交易错误", "因业务原因交易失败,请查看接口返回的详细信息"), OUT_TRADE_NO_USED_403(403, "OUT_TRADE_NO_USED", "商户订单号重复", "请核实商户订单号是否重复提交"), ACCOUNT_ERROR_403(403, "ACCOUNT_ERROR", "账号异常", "用户账号异常,无需更多操作"), @@ -46,6 +58,9 @@ public enum PayErrorMsgEnum { CONTRACT_NOT_CONFIRMED_403(403, "CONTRACT_NOT_CONFIRMED", "二级商户未开启手动提现权限", "二级商户号提现权限已关闭,无法发起提现"), ACCOUNT_NOT_VERIFIED_403(403, "ACCOUNT_NOT_VERIFIED", "二级商户下行打款未成功", "二级商户号结算银行卡信息有误,修改后重试"), + /** + * 404 错误 + */ REFUND_NOT_EXISTS_404(404, "REFUND_NOT_EXISTS", "订单号错误或订单状态不正确", "请检查订单号是否有误以及订单状态是否正确,如:未支付、已支付未退款"), ORDER_NO_TEXIST_404(404, "ORDER_NO_TEXIST", "订单不存在", "请检查订单是否发起过交易"), ORDER_NOT_EXIST_404(404, "ORDER_NOT_EXIST", "请求的资源不存在", "请商户检查需要查询的id或者请求URL是否正确"), @@ -55,10 +70,16 @@ public enum PayErrorMsgEnum { RESOURCE_NOT_EXISTS_404(404, "RESOURCE_NOT_EXISTS", "查询的资源不存在", "请检查查询资源的对应id是否填写正确"), NOT_FOUND_404(404, "NOT_FOUND", "请求的资源不存在", "请商户检查需要查询的id或者请求URL是否正确"), + /** + * 429 错误 + */ FREQUENCY_LIMITED_429(429, "FREQUENCY_LIMITED", "频率超限", "请求量不要超过接口调用频率限制"), RATELIMIT_EXCEEDED_429(429, "RATELIMIT_EXCEEDED", "频率限制", "请降低频率后重试"), FREQUENCY_LIMIT_EXCEED_429(429, "FREQUENCY_LIMIT_EXCEED", "接口限频", "请降低调用频率"), + /** + * 500 错误 + */ ERROR_500(500, "ERROR", "业务错误", "该错误都会返回具体的错误原因,请根据实际返回做相应处理"), OPENID_MISMATCH_500(500, "OPENID_MISMATCH", "openid和appid不匹配", "请确认openid和appid是否匹配"), BANK_ERROR_500(500, "BANK_ERROR", "银行系统异常", "银行系统异常,请用相同参数重新调用"), diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java b/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java new file mode 100644 index 00000000..3ff08498 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/PayManager.java @@ -0,0 +1,40 @@ +package ink.wgink.module.wechat.manager.pay.v3; + +import ink.wgink.module.wechat.utils.pay.PayCertificateUtil; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.security.cert.X509Certificate; + +/** + * @ClassName: PayMiniappManager + * @Description: 支付管理 + * @Author: wanggeng + * @Date: 2021/8/19 11:10 上午 + * @Version: 1.0 + */ +public class PayManager { + + private static final PayManager PAY_MANAGER = PayMiniappManagerBuilder.payManager; + private X509Certificate x509Certificate; + + private PayManager() { + } + + public static PayManager getInstance() { + return PAY_MANAGER; + } + + public X509Certificate getX509Certificate() { + return x509Certificate; + } + + public void setX509Certificate(String certificatePath) throws FileNotFoundException { + this.x509Certificate = PayCertificateUtil.getCertificate(new FileInputStream(certificatePath)); + } + + private static class PayMiniappManagerBuilder { + public static PayManager payManager = new PayManager(); + } + +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/miniapp/PayMiniappManager.java b/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/miniapp/PayMiniappManager.java deleted file mode 100644 index dd3d558b..00000000 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/manager/pay/v3/miniapp/PayMiniappManager.java +++ /dev/null @@ -1,25 +0,0 @@ -package ink.wgink.module.wechat.manager.pay.v3.miniapp; - -/** - * @ClassName: PayMiniappManager - * @Description: 小程序支付管理 - * @Author: wanggeng - * @Date: 2021/8/19 11:10 上午 - * @Version: 1.0 - */ -public class PayMiniappManager { - - private static final PayMiniappManager payMiniappManager = PayMiniappManagerBuilder.payMiniappManager; - - private PayMiniappManager() { - } - - public PayMiniappManager getInstance() { - return payMiniappManager; - } - - private static class PayMiniappManagerBuilder { - public static PayMiniappManager payMiniappManager = new PayMiniappManager(); - } - -} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayErrorResponse.java b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayErrorResponse.java new file mode 100644 index 00000000..de1d8e60 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/PayErrorResponse.java @@ -0,0 +1,107 @@ +package ink.wgink.module.wechat.pojo.pay.v3; + +/** + * @ClassName: ErrorResponse + * @Description: 错误返回 + * @Author: wanggeng + * @Date: 2021/8/19 6:02 下午 + * @Version: 1.0 + */ +public class PayErrorResponse { + + private String code; + private String message; + private Detail detail; + + public String getCode() { + return code == null ? "" : code.trim(); + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message == null ? "" : message.trim(); + } + + public void setMessage(String message) { + this.message = message; + } + + public Detail getDetail() { + return detail; + } + + public void setDetail(Detail detail) { + this.detail = detail; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + sb.append("\"code\":\"") + .append(code).append('\"'); + sb.append(",\"message\":\"") + .append(message).append('\"'); + sb.append(",\"detail\":") + .append(detail); + sb.append('}'); + return sb.toString(); + } + + public static class Detail { + private String field; + private String value; + private String issue; + private String location; + + public String getField() { + return field == null ? "" : field.trim(); + } + + public void setField(String field) { + this.field = field; + } + + public String getValue() { + return value == null ? "" : value.trim(); + } + + public void setValue(String value) { + this.value = value; + } + + public String getIssue() { + return issue == null ? "" : issue.trim(); + } + + public void setIssue(String issue) { + this.issue = issue; + } + + public String getLocation() { + return location == null ? "" : location.trim(); + } + + public void setLocation(String location) { + this.location = location; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + sb.append("\"field\":\"") + .append(field).append('\"'); + sb.append(",\"value\":\"") + .append(value).append('\"'); + sb.append(",\"issue\":\"") + .append(issue).append('\"'); + sb.append(",\"location\":\"") + .append(location).append('\"'); + sb.append('}'); + return sb.toString(); + } + } + +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/bos/pay/v3/PlaceOrderBO.java b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPlaceOrder.java similarity index 91% rename from module-wechat/src/main/java/ink/wgink/module/wechat/pojo/bos/pay/v3/PlaceOrderBO.java rename to module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPlaceOrder.java index 491e8a20..1894e7c8 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/bos/pay/v3/PlaceOrderBO.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPlaceOrder.java @@ -1,4 +1,4 @@ -package ink.wgink.module.wechat.pojo.bos.pay.v3; +package ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo; import ink.wgink.annotation.*; @@ -11,7 +11,7 @@ import java.util.List; * @Date: 2021/8/19 1:52 下午 * @Version: 1.0 */ -public class PlaceOrderBO { +public class PayPlaceOrder { @CheckEmptyAnnotation(name = "应用ID") private String appid; @@ -395,6 +395,47 @@ public class PlaceOrderBO { * 商户端设备号 */ private String deviceId; + /** + * 商户门店信息 + */ + private StoreInfo storeInfo; + + public String getPayerClientIp() { + return payerClientIp == null ? "" : payerClientIp.trim(); + } + + public void setPayerClientIp(String payerClientIp) { + this.payerClientIp = payerClientIp; + } + + public String getDeviceId() { + return deviceId == null ? "" : deviceId.trim(); + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public StoreInfo getStoreInfo() { + return storeInfo; + } + + public void setStoreInfo(StoreInfo storeInfo) { + this.storeInfo = storeInfo; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + sb.append("\"payerClientIp\":\"") + .append(payerClientIp).append('\"'); + sb.append(",\"deviceId\":\"") + .append(deviceId).append('\"'); + sb.append(",\"storeInfo\":") + .append(storeInfo); + sb.append('}'); + return sb.toString(); + } /** * 商户门店信息 diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPrepay.java b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPrepay.java new file mode 100644 index 00000000..76b51c44 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/pojo/pay/v3/jsapi/pojo/PayPrepay.java @@ -0,0 +1,30 @@ +package ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo; + +/** + * @ClassName: Prepay + * @Description: 预支付 + * @Author: wanggeng + * @Date: 2021/8/19 5:38 下午 + * @Version: 1.0 + */ +public class PayPrepay { + + private String prepayId; + + public String getPrepayId() { + return prepayId == null ? "" : prepayId.trim(); + } + + public void setPrepayId(String prepayId) { + this.prepayId = prepayId; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("{"); + sb.append("\"prepayId\":\"") + .append(prepayId).append('\"'); + sb.append('}'); + return sb.toString(); + } +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/AbstractPayRequest.java b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java similarity index 67% rename from module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/AbstractPayRequest.java rename to module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java index 78deef89..e7d98b33 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/AbstractPayRequest.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/AbstractPayRequest.java @@ -1,11 +1,22 @@ -package ink.wgink.module.wechat.utils.pay; +package ink.wgink.module.wechat.request.pay.v3; +import com.alibaba.fastjson.JSONObject; +import ink.wgink.exceptions.base.SystemException; +import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum; +import ink.wgink.module.wechat.pojo.pay.v3.PayErrorResponse; +import ink.wgink.module.wechat.utils.pay.PaySignAuthorizationUtil; +import ink.wgink.properties.wechat.pay.v3.PayProperties; +import ink.wgink.util.UUIDUtil; +import ink.wgink.util.string.WStringUtil; import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.core.io.FileSystemResource; import org.springframework.http.*; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.client.RestTemplate; import javax.crypto.Cipher; @@ -13,6 +24,7 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; @@ -27,9 +39,11 @@ import java.util.Base64; * @Date: 2021/8/18 9:41 下午 * @Version: 1.0 */ -public abstract class AbstractPayRequest { +public abstract class AbstractPayRequest { + protected static final Logger LOG = LoggerFactory.getLogger(AbstractPayRequest.class); public static final String PAY_SERIAL = "Wechatpay-Serial"; + private RestTemplate restTemplate; public AbstractPayRequest() { @@ -39,7 +53,20 @@ public abstract class AbstractPayRequest { this.restTemplate = new RestTemplate(simpleClientHttpRequestFactory); } - public abstract void response(ResponseEntity responseEntity); + /** + * bodys实体转json字符串 + * + * @param requestBody + * @return + */ + public abstract String bodyJsonString(RequestBody requestBody); + + /** + * 响应结果处理 + * + * @param responseEntity + */ + public abstract Response response(ResponseEntity responseEntity); /** * GET 请求 @@ -48,10 +75,11 @@ public abstract class AbstractPayRequest { * @param authorization 授权信息 * @param serialNumber 公钥证书序列号 */ - public void get(String url, String authorization, String serialNumber) { + public Response get(String url, String authorization, String serialNumber) { HttpEntity httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber)); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class); - response(responseEntity); + checkResponseCode(responseEntity); + return response(responseEntity); } /** @@ -62,10 +90,11 @@ public abstract class AbstractPayRequest { * @param serialNumber 公钥证书序列号 * @param jsonBody 请求参数 */ - public void post(String url, String authorization, String serialNumber, String jsonBody) { + public Response post(String url, String authorization, String serialNumber, String jsonBody) { HttpEntity httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber)); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class); - response(responseEntity); + checkResponseCode(responseEntity); + return response(responseEntity); } /** @@ -76,10 +105,11 @@ public abstract class AbstractPayRequest { * @param serialNumber 公钥证书序列号 * @param jsonBody 请求参数 */ - public void put(String url, String authorization, String serialNumber, String jsonBody) { + public Response put(String url, String authorization, String serialNumber, String jsonBody) { HttpEntity httpEntity = new HttpEntity(jsonBody, getHttpHeaders(authorization, serialNumber)); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.PUT, httpEntity, String.class); - response(responseEntity); + checkResponseCode(responseEntity); + return response(responseEntity); } /** @@ -89,10 +119,11 @@ public abstract class AbstractPayRequest { * @param authorization 授权信息 * @param serialNumber 公钥证书序列号 */ - public void delete(String url, String authorization, String serialNumber) { + public Response delete(String url, String authorization, String serialNumber) { HttpEntity httpEntity = new HttpEntity(getHttpHeaders(authorization, serialNumber)); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.DELETE, httpEntity, String.class); - response(responseEntity); + checkResponseCode(responseEntity); + return response(responseEntity); } /** @@ -104,14 +135,34 @@ public abstract class AbstractPayRequest { * @param jsonData 请求参数 * @param uploadFile 上传文件 */ - public void upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) { + public Response upload(String url, String authorization, String serialNumber, String jsonData, File uploadFile) { FileSystemResource fileSystemResource = new FileSystemResource(uploadFile); MultiValueMap form = new LinkedMultiValueMap<>(); form.add("file", fileSystemResource); form.add("meta", jsonData); HttpEntity> httpEntity = new HttpEntity<>(form, getUploadHeaders(authorization, serialNumber)); ResponseEntity responseEntity = restTemplate.exchange(url, HttpMethod.POST, httpEntity, String.class); - response(responseEntity); + checkResponseCode(responseEntity); + return response(responseEntity); + } + + /** + * 认证头 + * + * @param requestMethod + * @param urlSuffix + * @param payProperties + * @param jsonBody + * @param payAuthorizationTypeEnum + * @return + * @throws FileNotFoundException + */ + public String getAuthorization(RequestMethod requestMethod, String urlSuffix, String serialNumber, PayProperties payProperties, String jsonBody, PayAuthorizationTypeEnum payAuthorizationTypeEnum) throws Exception { + String randomSubStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase(); + return PaySignAuthorizationUtil.buildAuthorization(requestMethod, urlSuffix, + payProperties.getMchid(), serialNumber, + payProperties.getKeyPath(), jsonBody, randomSubStr, System.currentTimeMillis(), + payAuthorizationTypeEnum.getValue()); } /** @@ -159,6 +210,20 @@ public abstract class AbstractPayRequest { return httpHeaders; } + /** + * 检查返回状态码 + * + * @param responseEntity + */ + private void checkResponseCode(ResponseEntity responseEntity) { + if (responseEntity.getStatusCode().value() == HttpStatus.OK.value()) { + return; + } + LOG.error(responseEntity.getBody()); + PayErrorResponse payErrorResponse = JSONObject.parseObject(responseEntity.getBody(), PayErrorResponse.class); + throw new SystemException(payErrorResponse.getMessage()); + } + /** * Aes 解密 */ diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java new file mode 100644 index 00000000..51c70351 --- /dev/null +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/request/pay/v3/jsapi/PlaceOrderPayRequestImpl.java @@ -0,0 +1,155 @@ +package ink.wgink.module.wechat.request.pay.v3.jsapi; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; +import ink.wgink.module.wechat.request.pay.v3.AbstractPayRequest; +import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder; +import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay; +import org.springframework.http.ResponseEntity; + +import java.util.List; + +/** + * @ClassName: PlaceOrderPayRequest + * @Description: 下单请求 + * @Author: wanggeng + * @Date: 2021/8/19 3:22 下午 + * @Version: 1.0 + */ +public class PlaceOrderPayRequestImpl extends AbstractPayRequest { + + @Override + public String bodyJsonString(PayPlaceOrder payPlaceOrder) { + JSONObject bodyObject = new JSONObject(); + bodyObject.put("appid", payPlaceOrder.getAppid()); + bodyObject.put("mchid", payPlaceOrder.getMchid()); + bodyObject.put("description", payPlaceOrder.getDescription()); + bodyObject.put("out_trade_no", payPlaceOrder.getOutTradeNo()); + bodyObject.put("time_expire", payPlaceOrder.getTimeExpire()); + bodyObject.put("attach", payPlaceOrder.getAttach()); + bodyObject.put("notify_url", payPlaceOrder.getNotifyUrl()); + bodyObject.put("goods_tag", payPlaceOrder.getGoodsTag()); + bodyObject.put("amount", getAmountJsonObject(payPlaceOrder.getAmount())); + if (payPlaceOrder.getDetail() != null) { + bodyObject.put("detail", payPlaceOrder.getDetail()); + } + if (payPlaceOrder.getSceneInfo() != null) { + bodyObject.put("scene_info", payPlaceOrder.getSceneInfo()); + } + if (payPlaceOrder.getSettleInfo() != null) { + bodyObject.put("settle_info", payPlaceOrder.getSettleInfo()); + } + return bodyObject.toJSONString(); + } + + @Override + public PayPrepay response(ResponseEntity responseEntity) { + + return null; + } + + /** + * 订单金额 + * + * @param amount + * @return + */ + private JSONObject getAmountJsonObject(PayPlaceOrder.Amount amount) { + JSONObject amountJsonObject = new JSONObject(); + amountJsonObject.put("total", amount.getTotal()); + amountJsonObject.put("currency", amount.getCurrency()); + return amountJsonObject; + } + + /** + * 支付者 + * + * @param payer + * @return + */ + private JSONObject getPayer(PayPlaceOrder.Payer payer) { + JSONObject payerJsonObject = new JSONObject(); + payerJsonObject.put("openid", payer.getOpenid()); + return payerJsonObject; + } + + /** + * 优惠功能 + * + * @param detail + * @return + */ + private JSONObject getDetail(PayPlaceOrder.Detail detail) { + JSONObject detailJsonObject = new JSONObject(); + detailJsonObject.put("cost_price", detail.getCostPrice()); + detailJsonObject.put("invoice_id", detail.getInvoiceId()); + if (detail.getGoodsDetail() != null || !detail.getGoodsDetail().isEmpty()) { + detailJsonObject.put("goods_detail", listGoodsDetail(detail.getGoodsDetail())); + } + return detailJsonObject; + } + + /** + * 商品列表 + * + * @param goodsList + * @return + */ + private JSONArray listGoodsDetail(List goodsList) { + JSONArray goodsDetailJsonArray = new JSONArray(); + for (PayPlaceOrder.Detail.Goods goods : goodsList) { + JSONObject goodsJsonObject = new JSONObject(); + goodsJsonObject.put("merchant_goods_id", goods.getMerchantGoodsId()); + goodsJsonObject.put("wechatpay_goods_id", goods.getWechatpayGoodsId()); + goodsJsonObject.put("goods_name", goods.getGoodsName()); + goodsJsonObject.put("quantity", goods.getQuantity()); + goodsJsonObject.put("unit_price", goods.getUnitPrice()); + goodsDetailJsonArray.add(goodsJsonObject); + } + return goodsDetailJsonArray; + } + + /** + * 场景信息 + * + * @param sceneInfo + * @return + */ + private JSONObject getSceneInfo(PayPlaceOrder.SceneInfo sceneInfo) { + JSONObject sceneInfoJsonObject = new JSONObject(); + sceneInfoJsonObject.put("payer_client_ip", sceneInfo.getPayerClientIp()); + sceneInfoJsonObject.put("device_id", sceneInfo.getDeviceId()); + if (sceneInfo.getStoreInfo() != null) { + sceneInfoJsonObject.put("store_info", getStoreInfo(sceneInfo.getStoreInfo())); + } + return sceneInfoJsonObject; + } + + /** + * 商户门店信息 + * + * @param storeInfo + * @return + */ + private JSONObject getStoreInfo(PayPlaceOrder.SceneInfo.StoreInfo storeInfo) { + JSONObject storeInfoJsonObject = new JSONObject(); + storeInfoJsonObject.put("id", storeInfo.getId()); + storeInfoJsonObject.put("name", storeInfo.getName()); + storeInfoJsonObject.put("area_code", storeInfo.getAreaCode()); + storeInfoJsonObject.put("address", storeInfo.getAddress()); + return storeInfoJsonObject; + } + + /** + * 是否指定分账 + * + * @param settleInfo + * @return + */ + private JSONObject getSettleInfo(PayPlaceOrder.SettleInfo settleInfo) { + JSONObject settleJsonObject = new JSONObject(); + settleJsonObject.put("profit_sharing", settleInfo.getProfitSharing()); + return settleJsonObject; + } + +} diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/IJsapiService.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/IJsapiService.java index a18610fa..3c03724c 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/IJsapiService.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/IJsapiService.java @@ -1,5 +1,7 @@ package ink.wgink.module.wechat.service.pay.v3.jsapi; +import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder; + /** * @ClassName: IJsapiService * @Description: Jsapi业务 @@ -8,4 +10,13 @@ package ink.wgink.module.wechat.service.pay.v3.jsapi; * @Version: 1.0 */ public interface IJsapiService { + + /** + * 下单 + * + * @param payPlaceOrder + * @return 预支付交易会话标识,预支付交易会话标识。用于后续接口调用中使用,该值有效期为2小时 + */ + String placeOrder(PayPlaceOrder payPlaceOrder) throws Exception; + } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java index da25bafd..43d22795 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/service/pay/v3/jsapi/impl/JsapiServiceImpl.java @@ -1,8 +1,20 @@ package ink.wgink.module.wechat.service.pay.v3.jsapi.impl; import ink.wgink.common.base.DefaultBaseService; +import ink.wgink.module.wechat.enums.PayAuthorizationTypeEnum; +import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPlaceOrder; +import ink.wgink.module.wechat.request.pay.v3.jsapi.PlaceOrderPayRequestImpl; +import ink.wgink.module.wechat.pojo.pay.v3.jsapi.pojo.PayPrepay; import ink.wgink.module.wechat.service.pay.v3.jsapi.IJsapiService; +import ink.wgink.module.wechat.utils.pay.PayCertificateUtil; +import ink.wgink.properties.wechat.pay.v3.PayProperties; +import ink.wgink.util.BeanPropertyCheckUtil; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.RequestMethod; + +import java.io.FileInputStream; +import java.security.cert.X509Certificate; /** * @ClassName: JsapiServiceImpl @@ -14,6 +26,24 @@ import org.springframework.stereotype.Service; @Service public class JsapiServiceImpl extends DefaultBaseService implements IJsapiService { + @Autowired + private PayProperties payProperties; + @Override + public String placeOrder(PayPlaceOrder payPlaceOrder) throws Exception { + BeanPropertyCheckUtil.checkField(payPlaceOrder); + String urlSuffix = "/v3/pay/transactions/jsapi"; + String url = "https://api.mch.weixin.qq.com" + urlSuffix; + // 获得证书 + X509Certificate certificate = PayCertificateUtil.getCertificate(new FileInputStream(payProperties.getCertificatePath())); + String serialNumber = certificate.getSerialNumber().toString(16); + + PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl(); + String body = placeOrderPayRequest.bodyJsonString(payPlaceOrder); + String authorization = placeOrderPayRequest.getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body, PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048); + // 请求获得结果 + PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body); + return payPrepay.getPrepayId(); + } } diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java b/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java index 28fcfeff..b07e472d 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/startup/WechatStartUp.java @@ -2,8 +2,10 @@ package ink.wgink.module.wechat.startup; import ink.wgink.module.wechat.manager.MiniappManager; import ink.wgink.module.wechat.manager.OfficialAccountAccessTokenManager; +import ink.wgink.module.wechat.manager.pay.v3.PayManager; import ink.wgink.properties.wechat.miniapp.MiniappProperties; import ink.wgink.properties.wechat.official.account.OfficialAccountProperties; +import ink.wgink.properties.wechat.pay.v3.PayProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -29,9 +31,14 @@ public class WechatStartUp implements ApplicationRunner { private MiniappProperties miniAppProperties; @Autowired private OfficialAccountProperties officialAccountProperties; + @Autowired + private PayProperties payProperties; @Override public void run(ApplicationArguments args) throws Exception { + if (payProperties.getActive()) { + PayManager.getInstance().setX509Certificate(payProperties.getCertificatePath()); + } new Thread(() -> { refreshOfficialAccountAccessToken(); }).start(); diff --git a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayPrivateKeyUtil.java b/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayPrivateKeyUtil.java index 0e4f69bb..e06f7e0d 100644 --- a/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayPrivateKeyUtil.java +++ b/module-wechat/src/main/java/ink/wgink/module/wechat/utils/pay/PayPrivateKeyUtil.java @@ -42,7 +42,6 @@ public class PayPrivateKeyUtil { apiClientKeySB.append(line); } String originalKey = apiClientKeySB.toString(); - System.out.println(originalKey); String privateKey = originalKey .replace("-----BEGIN PRIVATE KEY-----", "") .replace("-----END PRIVATE KEY-----", "")