增加微信支付退款,退款查询,退款通知功能

This commit is contained in:
wanggeng 2022-11-12 00:39:13 +08:00
parent 55e5a56159
commit 0536fc4488
30 changed files with 1787 additions and 263 deletions

View File

@ -23,7 +23,7 @@ import java.io.IOException;
* @Date: 2021/8/19 9:36 下午
* @Version: 1.0
*/
@Api(tags = ISystemConstant.API_TAGS_WECHAT_PREFIX + "微信公众号")
@Api(tags = ISystemConstant.API_TAGS_WECHAT_PREFIX + "微信支付")
@RestController
@RequestMapping(ISystemConstant.WECHAT_PREFIX + "/pay")
public class PayController extends DefaultBaseController {
@ -38,7 +38,8 @@ public class PayController extends DefaultBaseController {
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = PayErrorResponse.class)})
@PostMapping("notice")
@CheckRequestBodyAnnotation
public void notice(HttpServletRequest request, HttpServletResponse response,
public void notice(HttpServletRequest request,
HttpServletResponse response,
@RequestHeader("Wechatpay-Nonce") String nonce,
@RequestHeader("Wechatpay-Timestamp") String timestamp,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature) throws IOException {

View File

@ -0,0 +1,47 @@
package ink.wgink.module.wechat.pay.controller.wechat.v3;
import ink.wgink.common.base.DefaultBaseController;
import ink.wgink.interfaces.consts.ISystemConstant;
import ink.wgink.module.wechat.pay.pojo.v3.PayErrorResponse;
import ink.wgink.module.wechat.pay.service.v3.refund.IRefundService;
import io.swagger.annotations.*;
import org.springframework.beans.factory.annotation.Autowired;
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 javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: RefundController
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/11 23:30
* @Version: 1.0
*/
@Api(tags = ISystemConstant.API_TAGS_WECHAT_PREFIX + "微信退款")
@RestController
@RequestMapping(ISystemConstant.WECHAT_PREFIX + "/refund")
public class RefundController extends DefaultBaseController {
@Autowired
private IRefundService refundService;
@ApiOperation(value = "通知退款结果", notes = "有微信支付主动发起的退款结果通知接口")
@ApiImplicitParams({
@ApiImplicitParam(name = "Wechatpay-Signature", value = "来自微信的签名值", paramType = "header")
})
@ApiResponses({@ApiResponse(code = 400, message = "请求失败", response = PayErrorResponse.class)})
@PostMapping("notice")
public void notice(HttpServletRequest request,
HttpServletResponse response,
@RequestHeader("Wechatpay-Nonce") String nonce,
@RequestHeader("Wechatpay-Timestamp") String timestamp,
@RequestHeader("Wechatpay-Signature") String wechatpaySignature) throws IOException {
refundService.notice(request, response, nonce, timestamp, wechatpaySignature);
}
}

View File

@ -0,0 +1,30 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: RefundsAmountFromAccountEnum
* @Description: 出资账户类型
* @Author: wanggeng
* @Date: 2022/11/11 17:10
* @Version: 1.0
*/
public enum RefundAmountFromAccountEnum {
AVAILABLE("AVAILABLE", "可用余额"),
UNAVAILABLE("UNAVAILABLE", "不可用余额");
private String value;
private String text;
RefundAmountFromAccountEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -0,0 +1,32 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: RefundsChannelEnum
* @Description: 退款渠道枚举
* @Author: wanggeng
* @Date: 2022/11/10 21:34
* @Version: 1.0
*/
public enum RefundChannelEnum {
ORIGINAL("ORIGINAL", "原路退款"),
BALANCE("BALANCE", "退回到余额"),
OTHER_BALANCE("OTHER_BALANCE", "原账户异常退到其他余额账户"),
OTHER_BANKCARD("OTHER_BANKCARD", "原银行卡异常退到其他银行卡");
private String value;
private String text;
RefundChannelEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -0,0 +1,33 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: FundsAccountEnum
* @Description: 资金账户
* @Author: wanggeng
* @Date: 2022/11/11 09:41
* @Version: 1.0
*/
public enum RefundFundsAccountEnum {
UNSETTLED("UNSETTLED", "未结算资金"),
AVAILABLE("AVAILABLE", "可用余额"),
UNAVAILABLE("UNAVAILABLE", "不可用余额"),
OPERATION("OPERATION", "运营户"),
BASIC("BASIC", "基本账户(含可用余额和不可用余额)");
private String value;
private String text;
RefundFundsAccountEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -0,0 +1,30 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: RefundsPromotionDetailScopeEnum
* @Description: 退款优惠范围枚举
* @Author: wanggeng
* @Date: 2022/11/11 17:59
* @Version: 1.0
*/
public enum RefundPromotionDetailScopeEnum {
GLOBAL("GLOBAL", "全场代金券"),
SINGLE("SINGLE", "单品优惠");
private String value;
private String text;
RefundPromotionDetailScopeEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -0,0 +1,30 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: RefundsPromotionDetailTypeEnum
* @Description: 退款优惠类型枚举
* @Author: wanggeng
* @Date: 2022/11/11 18:01
* @Version: 1.0
*/
public enum RefundPromotionDetailTypeEnum {
COUPON("COUPON", "代金券,需要走结算资金的充值型代金券"),
DISCOUNT("DISCOUNT", "优惠券,不走结算资金的免充值型优惠券");
private String value;
private String text;
RefundPromotionDetailTypeEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -0,0 +1,32 @@
package ink.wgink.module.wechat.pay.enums;
/**
* @ClassName: RefundsStatusEnum
* @Description: 退款状态
* @Author: wanggeng
* @Date: 2022/11/10 21:41
* @Version: 1.0
*/
public enum RefundStatusEnum {
SUCCESS("SUCCESS","退款成功"),
CLOSED("CLOSED", "退款关闭"),
PROCESSING("PROCESSING", "退款处理中"),
ABNORMAL("ABNORMAL", "退款异常");
private String value;
private String text;
RefundStatusEnum(String value, String text) {
this.value = value;
this.text = text;
}
public String getValue() {
return value == null ? "" : value.trim();
}
public String getText() {
return text == null ? "" : text.trim();
}
}

View File

@ -10,7 +10,7 @@ import ink.wgink.annotation.CheckEmptyAnnotation;
* @Date: 2021/8/20 8:07 上午
* @Version: 1.0
*/
public class PayNotice {
public class Notice {
@CheckEmptyAnnotation(name = "通知ID")
private String id;

View File

@ -35,6 +35,10 @@ public class PayPlaceOrder {
* 订单优惠标记
*/
private String goodsTag;
/**
* 电子发票入口开放标识
*/
private Boolean supportFapiao;
@CheckNullAnnotation(name = "订单金额")
@CheckBeanAnnotation
private Amount amount;
@ -121,6 +125,14 @@ public class PayPlaceOrder {
this.goodsTag = goodsTag;
}
public Boolean getSupportFapiao() {
return supportFapiao == null ? false : supportFapiao;
}
public void setSupportFapiao(Boolean supportFapiao) {
this.supportFapiao = supportFapiao;
}
public Amount getAmount() {
return amount;
}

View File

@ -9,21 +9,21 @@ package ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo;
*/
public class PayPrepay {
private String prepayId;
private String prepay_id;
public String getPrepayId() {
return prepayId == null ? "" : prepayId.trim();
public String getPrepay_id() {
return prepay_id == null ? "" : prepay_id.trim();
}
public void setPrepayId(String prepayId) {
this.prepayId = prepayId;
public void setPrepay_id(String prepay_id) {
this.prepay_id = prepay_id;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"prepayId\":\"")
.append(prepayId).append('\"');
.append(prepay_id).append('\"');
sb.append('}');
return sb.toString();
}

View File

@ -0,0 +1,30 @@
package ink.wgink.module.wechat.pay.pojo.v3.nav;
/**
* @ClassName: PayCodeUrl
* @Description: native下单
* @Author: wanggeng
* @Date: 2022/11/10 17:55
* @Version: 1.0
*/
public class PayCodeUrl {
private String code_url;
public String getCode_url() {
return code_url == null ? "" : code_url.trim();
}
public void setCode_url(String code_url) {
this.code_url = code_url;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"codeUrl\":\"")
.append(code_url).append('\"');
sb.append('}');
return sb.toString();
}
}

View File

@ -0,0 +1,40 @@
package ink.wgink.module.wechat.pay.pojo.v3.refund;
import ink.wgink.module.wechat.pay.enums.RefundAmountFromAccountEnum;
/**
* @ClassName: AmountFrom
* @Description: 退款出资账户及金额
* @Author: wanggeng
* @Date: 2022/11/11 18:41
* @Version: 1.0
*/
public class AmountFrom {
/**
* 出资账户类型
*/
private RefundAmountFromAccountEnum account;
/**
* 出资金额<br/>
* 对应账户出资金额
*/
private Integer amount;
public RefundAmountFromAccountEnum getAccount() {
return account;
}
public void setAccount(RefundAmountFromAccountEnum account) {
this.account = account;
}
public Integer getAmount() {
return amount == null ? 0 : amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
}

View File

@ -0,0 +1,90 @@
package ink.wgink.module.wechat.pay.pojo.v3.refund;
/**
* @ClassName: GoodsDetail
* @Description: 退款商品
* @Author: wanggeng
* @Date: 2022/11/11 18:44
* @Version: 1.0
*/
public class GoodsDetail {
/**
* 商户侧商品编码<br/>
* 由半角的大小写字母数字中划线下划线中的一种或几种组成
*/
private String merchant_goods_id;
/**
* 微信支付商品编码<br/>
* 微信支付定义的统一商品编号没有可不传
*/
private String wechatpay_goods_id;
/**
* 商品名称<br/>
* 商品的实际名称
*/
private String goods_name;
/**
* 商品单价<br/>
* 商品单价金额单位为分
*/
private Integer unit_price;
/**
* 商品退款金额<br/>
* 商品退款金额单位为分
*/
private Integer refund_amount;
/**
* 商品退货数量
*/
private Integer refund_quantity;
public String getMerchant_goods_id() {
return merchant_goods_id == null ? "" : merchant_goods_id.trim();
}
public void setMerchant_goods_id(String merchant_goods_id) {
this.merchant_goods_id = merchant_goods_id;
}
public String getWechatpay_goods_id() {
return wechatpay_goods_id == null ? "" : wechatpay_goods_id.trim();
}
public void setWechatpay_goods_id(String wechatpay_goods_id) {
this.wechatpay_goods_id = wechatpay_goods_id;
}
public String getGoods_name() {
return goods_name == null ? "" : goods_name.trim();
}
public void setGoods_name(String goods_name) {
this.goods_name = goods_name;
}
public Integer getUnit_price() {
return unit_price == null ? 0 : unit_price;
}
public void setUnit_price(Integer unit_price) {
this.unit_price = unit_price;
}
public Integer getRefund_amount() {
return refund_amount == null ? 0 : refund_amount;
}
public void setRefund_amount(Integer refund_amount) {
this.refund_amount = refund_amount;
}
public Integer getRefund_quantity() {
return refund_quantity == null ? 0 : refund_quantity;
}
public void setRefund_quantity(Integer refund_quantity) {
this.refund_quantity = refund_quantity;
}
}

View File

@ -0,0 +1,132 @@
package ink.wgink.module.wechat.pay.pojo.v3.refund;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: Refund
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/11 18:49
* @Version: 1.0
*/
public class Refund {
private String out_refund_no;
private String reason;
private String notify_url;
private String funds_account;
private Amount amount;
private List<GoodsDetail> goods_detail;
public String getOut_refund_no() {
return out_refund_no == null ? "" : out_refund_no.trim();
}
public void setOut_refund_no(String out_refund_no) {
this.out_refund_no = out_refund_no;
}
public String getReason() {
return reason == null ? "" : reason.trim();
}
public void setReason(String reason) {
this.reason = reason;
}
public String getNotify_url() {
return notify_url == null ? "" : notify_url.trim();
}
public void setNotify_url(String notify_url) {
this.notify_url = notify_url;
}
public String getFunds_account() {
return funds_account == null ? "" : funds_account.trim();
}
public void setFunds_account(String funds_account) {
this.funds_account = funds_account;
}
public Amount getAmount() {
return amount;
}
public void setAmount(Amount amount) {
this.amount = amount;
}
public List<GoodsDetail> getGoods_detail() {
return goods_detail == null ? new ArrayList() : goods_detail;
}
public void setGoods_detail(List<GoodsDetail> goods_detail) {
this.goods_detail = goods_detail;
}
public static class Amount {
/**
* 退款金额<br/>
* 退款金额单位为分只能为整数不能超过原订单支付金额
*/
private Integer refund;
/**
* 退款出资账户及金额<br/>
* 退款需要从指定账户出资时传递此参数指定出资金额币种的最小单位只能为整数<br/>
* 同时指定多个账户出资退款的使用场景需要满足以下条件<br/>
* 1未开通退款支出分离产品功能<br/>
* 2订单属于分账订单且分账处于待分账或分账中状态<br/>
* 参数传递需要满足条件<br/>
* 1基本账户可用余额出资金额与基本账户不可用余额出资金额之和等于退款金额<br/>
* 2账户类型不能重复<br/>
* 上述任一条件不满足将返回错误
*/
private List<AmountFrom> from;
/**
* 原订单金额<br/>
* 原支付交易的订单总金额单位为分只能为整数
*/
private Integer total;
/**
* 退款币种<br/>
* 符合ISO 4217标准的三位字母代码目前只支持人民币CNY
*/
private String currency;
public Integer getRefund() {
return refund == null ? 0 : refund;
}
public void setRefund(Integer refund) {
this.refund = refund;
}
public List<AmountFrom> getFrom() {
return from == null ? new ArrayList() : from;
}
public void setFrom(List<AmountFrom> from) {
this.from = from;
}
public Integer getTotal() {
return total == null ? 0 : total;
}
public void setTotal(Integer total) {
this.total = total;
}
public String getCurrency() {
return currency == null ? "" : currency.trim();
}
public void setCurrency(String currency) {
this.currency = currency;
}
}
}

View File

@ -0,0 +1,205 @@
package ink.wgink.module.wechat.pay.pojo.v3.refund;
import ink.wgink.module.wechat.pay.enums.RefundStatusEnum;
/**
* @ClassName: RefundNoticeCiphertext
* @Description: 退款通知密文
* @Author: wanggeng
* @Date: 2022/11/12 00:00
* @Version: 1.0
*/
public class RefundNoticeCiphertext {
/**
* 直连商户号<br/>
* 直连商户的商户号由微信支付生成并下发
*/
private String mchid;
/**
* 商户订单号<br/>
* 返回的商户订单号
*/
private String out_trade_no;
/**
* 微信支付订单号
*/
private String transaction_id;
/**
* 商户退款单号
*/
private String out_refund_no;
/**
* 微信支付退款单号
*/
private String refund_id;
/**
* 退款状态
*/
private RefundStatusEnum refund_status;
/**
* 退款成功时间
*/
private String success_time;
/**
* 退款入账账户
*/
private String user_received_account;
/**
* 金额信息
*/
private Amount amount;
public String getMchid() {
return mchid == null ? "" : mchid.trim();
}
public void setMchid(String mchid) {
this.mchid = mchid;
}
public String getOut_trade_no() {
return out_trade_no == null ? "" : out_trade_no.trim();
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public String getTransaction_id() {
return transaction_id == null ? "" : transaction_id.trim();
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getOut_refund_no() {
return out_refund_no == null ? "" : out_refund_no.trim();
}
public void setOut_refund_no(String out_refund_no) {
this.out_refund_no = out_refund_no;
}
public String getRefund_id() {
return refund_id == null ? "" : refund_id.trim();
}
public void setRefund_id(String refund_id) {
this.refund_id = refund_id;
}
public RefundStatusEnum getRefund_status() {
return refund_status;
}
public void setRefund_status(RefundStatusEnum refund_status) {
this.refund_status = refund_status;
}
public String getSuccess_time() {
return success_time == null ? "" : success_time.trim();
}
public void setSuccess_time(String success_time) {
this.success_time = success_time;
}
public String getUser_received_account() {
return user_received_account == null ? "" : user_received_account.trim();
}
public void setUser_received_account(String user_received_account) {
this.user_received_account = user_received_account;
}
public Amount getAmount() {
return amount;
}
public void setAmount(Amount amount) {
this.amount = amount;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("{");
sb.append("\"mchid\":\"")
.append(mchid).append('\"');
sb.append(",\"out_trade_no\":\"")
.append(out_trade_no).append('\"');
sb.append(",\"transaction_id\":\"")
.append(transaction_id).append('\"');
sb.append(",\"out_refund_no\":\"")
.append(out_refund_no).append('\"');
sb.append(",\"refund_id\":\"")
.append(refund_id).append('\"');
sb.append(",\"refund_status\":")
.append(refund_status);
sb.append(",\"success_time\":\"")
.append(success_time).append('\"');
sb.append(",\"user_received_account\":\"")
.append(user_received_account).append('\"');
sb.append(",\"amount\":")
.append(amount);
sb.append('}');
return sb.toString();
}
public static class Amount {
/**
* 订单金额<br/>
* 订单总金额单位为分只能为整数详见<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2">支付金额</a>
*/
private Integer total;
/**
* 退款金额<br/>
* 退款金额币种的最小单位只能为整数不能超过原订单支付金额如果有使用券后台会按比例退
*/
private Integer refund;
/**
* 用户支付金额<br/>
* 用户实际支付金额单位为分只能为整数详见<a href="https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_2">支付金额</a>
*/
private Integer payer_total;
/**
* 用户退款金额<br/>
* 退款给用户的金额不包含所有优惠券金额
*/
private Integer payer_refund;
public Integer getTotal() {
return total == null ? 0 : total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getRefund() {
return refund == null ? 0 : refund;
}
public void setRefund(Integer refund) {
this.refund = refund;
}
public Integer getPayer_total() {
return payer_total == null ? 0 : payer_total;
}
public void setPayer_total(Integer payer_total) {
this.payer_total = payer_total;
}
public Integer getPayer_refund() {
return payer_refund == null ? 0 : payer_refund;
}
public void setPayer_refund(Integer payer_refund) {
this.payer_refund = payer_refund;
}
}
}

View File

@ -0,0 +1,392 @@
package ink.wgink.module.wechat.pay.pojo.v3.refund;
import ink.wgink.module.wechat.pay.enums.*;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: Refunds
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/10 21:31
* @Version: 1.0
*/
public class RefundResult {
/**
* 微信支付退款单号
*/
private String refund_id;
/**
* 商户退款单号<br/>
* 商户系统内部的退款单号商户系统内部唯一只能是数字大小写字母_-|*@ 同一退款单号多次请求只退一笔
*/
private String out_refund_no;
/**
* 微信支付交易订单号
*/
private String transaction_id;
/**
* 商户订单号<br/>
* 原支付交易对应的商户订单号
*/
private String out_trade_no;
/**
* 退款渠道
*/
private RefundChannelEnum channel;
/**
* 退款入账账户<br/>
* 取当前退款单的退款入账方有以下几种情况<br/>
* 1退回银行卡{银行名称}{卡类型}{卡尾号}<br/>
* 2退回支付用户零钱:支付用户零钱<br/>
* 3退还商户:商户基本账户商户结算银行账户<br/>
* 4退回支付用户零钱通:支付用户零钱通
*/
private String user_received_account;
/**
* 退款成功时间<br/>
* 退款成功时间当退款状态为退款成功时有返回
*/
private String success_time;
/**
* 退款创建时间<br/>
* 退款受理时间
*/
private String create_time;
/**
* 退款状态<br/>
* 退款到银行发现用户的卡作废或者冻结了导致原路退款银行卡失败可前往商户平台-交易中心手动处理此笔退款
*/
private RefundStatusEnum status;
/**
* 资金账户<br/>
* 退款所使用资金对应的资金账户类型
*/
private RefundFundsAccountEnum funds_account;
/**
* 金额信息
*/
private Amount amount;
/**
* 优惠退款信息
*/
private PromotionDetail promotion_detail;
public String getRefund_id() {
return refund_id == null ? "" : refund_id.trim();
}
public void setRefund_id(String refund_id) {
this.refund_id = refund_id;
}
public String getOut_refund_no() {
return out_refund_no == null ? "" : out_refund_no.trim();
}
public void setOut_refund_no(String out_refund_no) {
this.out_refund_no = out_refund_no;
}
public String getTransaction_id() {
return transaction_id == null ? "" : transaction_id.trim();
}
public void setTransaction_id(String transaction_id) {
this.transaction_id = transaction_id;
}
public String getOut_trade_no() {
return out_trade_no == null ? "" : out_trade_no.trim();
}
public void setOut_trade_no(String out_trade_no) {
this.out_trade_no = out_trade_no;
}
public RefundChannelEnum getChannel() {
return channel;
}
public void setChannel(RefundChannelEnum channel) {
this.channel = channel;
}
public String getUser_received_account() {
return user_received_account == null ? "" : user_received_account.trim();
}
public void setUser_received_account(String user_received_account) {
this.user_received_account = user_received_account;
}
public String getSuccess_time() {
return success_time == null ? "" : success_time.trim();
}
public void setSuccess_time(String success_time) {
this.success_time = success_time;
}
public String getCreate_time() {
return create_time == null ? "" : create_time.trim();
}
public void setCreate_time(String create_time) {
this.create_time = create_time;
}
public RefundStatusEnum getStatus() {
return status;
}
public void setStatus(RefundStatusEnum status) {
this.status = status;
}
public RefundFundsAccountEnum getFunds_account() {
return funds_account;
}
public void setFunds_account(RefundFundsAccountEnum funds_account) {
this.funds_account = funds_account;
}
public Amount getAmount() {
return amount;
}
public void setAmount(Amount amount) {
this.amount = amount;
}
public PromotionDetail getPromotion_detail() {
return promotion_detail;
}
public void setPromotion_detail(PromotionDetail promotion_detail) {
this.promotion_detail = promotion_detail;
}
/**
* 金额信息
*/
public static class Amount {
/**
* 订单金额<br/>
* 订单总金额单位为分
*/
private Integer total;
/**
* 退款金额<br/>
* 退款标价金额单位为分可以做部分退款
*/
private Integer refund;
/**
* 退款出资账户及金额
*/
private List<AmountFrom> from;
/**
* 用户支付金额<br/>
* 现金支付金额单位为分只能为整数
*/
private Integer payer_total;
/**
* 用户退款金额<br/>
* 退款给用户的金额不包含所有优惠券金额
*/
private Integer payer_refund;
/**
* 应结退款金额<br/>
* 去掉非充值代金券退款金额后的退款金额单位为分退款金额=申请退款金额-非充值代金券退款金额退款金额<=申请退款金额
*/
private Integer settlement_refund;
/**
* 应结订单金额<br/>
* 应结订单金额=订单金额-免充值代金券金额应结订单金额<=订单金额单位为分
*/
private Integer settlement_total;
/**
* 优惠退款金额<br/>
* 优惠退款金额<=退款金额退款金额-代金券或立减优惠退款金额为现金说明详见代金券或立减优惠单位为分
*/
private Integer discount_refund;
/**
* 退款币种<br/>
* 符合ISO 4217标准的三位字母代码目前只支持人民币CNY
*/
private String currency;
/**
* 手续费退款金额<br/>
* 手续费退款金额单位为分
*/
private Integer refund_fee;
public Integer getTotal() {
return total == null ? 0 : total;
}
public void setTotal(Integer total) {
this.total = total;
}
public Integer getRefund() {
return refund == null ? 0 : refund;
}
public void setRefund(Integer refund) {
this.refund = refund;
}
public List<AmountFrom> getFrom() {
return from == null ? new ArrayList() : from;
}
public void setFrom(List<AmountFrom> from) {
this.from = from;
}
public Integer getPayer_total() {
return payer_total == null ? 0 : payer_total;
}
public void setPayer_total(Integer payer_total) {
this.payer_total = payer_total;
}
public Integer getPayer_refund() {
return payer_refund == null ? 0 : payer_refund;
}
public void setPayer_refund(Integer payer_refund) {
this.payer_refund = payer_refund;
}
public Integer getSettlement_refund() {
return settlement_refund == null ? 0 : settlement_refund;
}
public void setSettlement_refund(Integer settlement_refund) {
this.settlement_refund = settlement_refund;
}
public Integer getSettlement_total() {
return settlement_total == null ? 0 : settlement_total;
}
public void setSettlement_total(Integer settlement_total) {
this.settlement_total = settlement_total;
}
public Integer getDiscount_refund() {
return discount_refund == null ? 0 : discount_refund;
}
public void setDiscount_refund(Integer discount_refund) {
this.discount_refund = discount_refund;
}
public String getCurrency() {
return currency == null ? "" : currency.trim();
}
public void setCurrency(String currency) {
this.currency = currency;
}
public Integer getRefund_fee() {
return refund_fee == null ? 0 : refund_fee;
}
public void setRefund_fee(Integer refund_fee) {
this.refund_fee = refund_fee;
}
}
/**
* 优惠退款信息
*/
public static class PromotionDetail {
/**
* 券ID
*/
private String promotion_id;
/**
* 优惠范围
*/
private RefundPromotionDetailScopeEnum scope;
/**
* 优惠类型
*/
private RefundPromotionDetailTypeEnum type;
/**
* 优惠券面额<br/>
* 用户享受优惠的金额优惠券面额=微信出资金额+商家出资金额+其他出资方金额 单位为分
*/
private Integer amount;
/**
* 优惠退款金额<br/>
* 优惠退款金额<=退款金额退款金额-代金券或立减优惠退款金额为用户支付的现金说明详见代金券或立减优惠单位为分
*/
private Integer refund_amount;
/**
* 商品列表<br/>
* 优惠商品发生退款时返回商品信息
*/
private List<GoodsDetail> goods_detail;
public String getPromotion_id() {
return promotion_id == null ? "" : promotion_id.trim();
}
public void setPromotion_id(String promotion_id) {
this.promotion_id = promotion_id;
}
public RefundPromotionDetailScopeEnum getScope() {
return scope;
}
public void setScope(RefundPromotionDetailScopeEnum scope) {
this.scope = scope;
}
public RefundPromotionDetailTypeEnum getType() {
return type;
}
public void setType(RefundPromotionDetailTypeEnum type) {
this.type = type;
}
public Integer getAmount() {
return amount == null ? 0 : amount;
}
public void setAmount(Integer amount) {
this.amount = amount;
}
public Integer getRefund_amount() {
return refund_amount == null ? 0 : refund_amount;
}
public void setRefund_amount(Integer refund_amount) {
this.refund_amount = refund_amount;
}
public List<GoodsDetail> getGoods_detail() {
return goods_detail == null ? new ArrayList<>() : goods_detail;
}
public void setGoods_detail(List<GoodsDetail> goods_detail) {
this.goods_detail = goods_detail;
}
}
}

View File

@ -6,6 +6,7 @@ import ink.wgink.annotation.rpc.rest.method.RemoteGetMethod;
import ink.wgink.annotation.rpc.rest.method.RemotePostMethod;
import ink.wgink.annotation.rpc.rest.params.*;
import ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.pay.pojo.v3.nav.PayCodeUrl;
import ink.wgink.module.wechat.pay.pojo.v3.order.OrderSearch;
/**
@ -33,6 +34,21 @@ public interface IOrderPayRemoteService {
@RemoteHeaderParams(IPayRemoteService.HEADER_WECHATPAY_SERIAL) String serialNumber,
@RemoteJsonBodyParams JSONObject payPlaceOrder);
/**
* 下单
*
* @param server
* @param authorization
* @param serialNumber
* @param payPlaceOrder
* @return
*/
@RemotePostMethod("/v3/pay/transactions/native")
PayCodeUrl placeOrderNative(@RemoteServerParams String server,
@RemoteHeaderParams(IPayRemoteService.HEADER_AUTHORIZATION) String authorization,
@RemoteHeaderParams(IPayRemoteService.HEADER_WECHATPAY_SERIAL) String serialNumber,
@RemoteJsonBodyParams JSONObject payPlaceOrder);
/**
* 关闭订单
*

View File

@ -0,0 +1,45 @@
package ink.wgink.module.wechat.pay.remote.v3;
import com.alibaba.fastjson2.JSONObject;
import ink.wgink.annotation.rpc.rest.RemoteService;
import ink.wgink.annotation.rpc.rest.method.RemoteGetMethod;
import ink.wgink.annotation.rpc.rest.method.RemotePostMethod;
import ink.wgink.annotation.rpc.rest.params.RemoteHeaderParams;
import ink.wgink.annotation.rpc.rest.params.RemoteJsonBodyParams;
import ink.wgink.annotation.rpc.rest.params.RemotePathParams;
import ink.wgink.annotation.rpc.rest.params.RemoteServerParams;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundResult;
/**
* @ClassName: IRefundsRemoteService
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/10 21:30
* @Version: 1.0
*/
@RemoteService
public interface IRefundsRemoteService {
/**
* 申请退款
*
* @param server
* @param authorization
* @param serialNumber
* @param body
* @return
*/
@RemotePostMethod("/v3/refund/domestic/refunds")
RefundResult refund(@RemoteServerParams String server,
@RemoteHeaderParams(IPayRemoteService.HEADER_AUTHORIZATION) String authorization,
@RemoteHeaderParams(IPayRemoteService.HEADER_WECHATPAY_SERIAL) String serialNumber,
@RemoteJsonBodyParams JSONObject body);
@RemoteGetMethod("/v3/refund/domestic/refunds/{outRefundNo}")
RefundResult get(@RemoteServerParams String server,
@RemoteHeaderParams(IPayRemoteService.HEADER_AUTHORIZATION) String authorization,
@RemoteHeaderParams(IPayRemoteService.HEADER_WECHATPAY_SERIAL) String serialNumber,
@RemotePathParams("out_refund_no") String outRefundNo);
}

View File

@ -13,7 +13,7 @@ import org.springframework.stereotype.Service;
* @Version: 1.0
*/
@Service
public class DefaultPayNoticeService extends DefaultBaseService implements IPayNoticeService {
public class DefaultPayNoticeServiceImpl extends DefaultBaseService implements IPayNoticeService {
@Override
public void handle(PayNoticeCiphertext payNoticeCiphertext) throws Exception {

View File

@ -2,28 +2,20 @@ package ink.wgink.module.wechat.pay.service.v3.impl;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.pay.enums.PayErrorMsgEnum;
import ink.wgink.module.wechat.pay.manager.v3.PayManager;
import ink.wgink.module.wechat.pay.pojo.v3.PayErrorResponse;
import ink.wgink.module.wechat.pay.pojo.v3.PayNotice;
import ink.wgink.module.wechat.pay.pojo.v3.Notice;
import ink.wgink.module.wechat.pay.pojo.v3.PayNoticeCiphertext;
import ink.wgink.module.wechat.pay.service.v3.IPayNoticeService;
import ink.wgink.module.wechat.pay.service.v3.IPayService;
import ink.wgink.module.wechat.pay.utils.v3.PayAesUtil;
import ink.wgink.module.wechat.pay.utils.v3.PayVerifyUtil;
import ink.wgink.module.wechat.pay.utils.v3.AsyncNoticeResultUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
/**
* @ClassName: IPayServiceImpl
@ -42,79 +34,22 @@ public class PayServiceImpl extends DefaultBaseService implements IPayService {
@Override
public void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException {
String payNoticeJsonString = getBodyString(request);
if (StringUtils.isBlank(payNoticeJsonString)) {
throw new ParamsException("请求body为空");
}
LOG.debug("payNoticeJsonString: {}", payNoticeJsonString);
PayNotice payNotice = JSONObject.parseObject(payNoticeJsonString, PayNotice.class);
try {
LOG.debug("wechatpaySignature: {}", wechatpaySignature);
LOG.debug("验证签名");
boolean checkVerify = PayVerifyUtil.verifySignature(wechatpaySignature, payNoticeJsonString, nonce, timestamp, PayManager.getInstance().getPlatformCertificate());
if (!checkVerify) {
errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401);
return;
}
LOG.debug("解密内容");
PayNotice.Resource resource = payNotice.getResource();
PayAesUtil aesUtil = new PayAesUtil(payProperties.getApiV3Secretkey().getBytes(StandardCharsets.UTF_8));
String payNoticeCiphertextJsonString = aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8), resource.getNonce().getBytes(StandardCharsets.UTF_8), resource.getCiphertext());
PayNoticeCiphertext payNoticeCiphertext = JSONObject.parseObject(payNoticeCiphertextJsonString, PayNoticeCiphertext.class);
Notice notice = AsyncNoticeResultUtil.getNotice(request, nonce, timestamp, wechatpaySignature);
String noticeCiphertextJsonString = AsyncNoticeResultUtil.getNoticeCiphertext(notice, payProperties.getApiV3Secretkey());
PayNoticeCiphertext payNoticeCiphertext = JSONObject.parseObject(noticeCiphertextJsonString, PayNoticeCiphertext.class);
payNoticeService.handle(payNoticeCiphertext);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
errorResult(response, PayErrorMsgEnum.ERROR_500);
if (e instanceof SystemException) {
AsyncNoticeResultUtil.errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401);
return;
}
AsyncNoticeResultUtil.errorResult(response, PayErrorMsgEnum.ERROR_500);
return;
}
successResult(response);
AsyncNoticeResultUtil.successResult(response);
}
/**
* 错误结果
*
* @param payErrorMsgEnum
*/
private void errorResult(HttpServletResponse response, PayErrorMsgEnum payErrorMsgEnum) {
PayErrorResponse payErrorResponse = new PayErrorResponse();
payErrorResponse.setCode(payErrorMsgEnum.getErrorCode());
payErrorResponse.setMessage(payErrorMsgEnum.getSummary());
response.setContentType("application/json; charset=UTF-8");
response.setStatus(payErrorMsgEnum.getCode());
try (PrintWriter writer = response.getWriter()) {
writer.println(JSONObject.toJSONString(payErrorResponse));
writer.flush();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
/**
* 成功结果
*/
private void successResult(HttpServletResponse response) {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
JSONObject successJsonObject = new JSONObject();
successJsonObject.put("code", "SUCCESS");
successJsonObject.put("message", "成功");
try (PrintWriter writer = response.getWriter()) {
writer.println(successJsonObject.toJSONString());
writer.flush();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
private String getBodyString(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder bodyString = new StringBuilder();
for (String line; (line = reader.readLine()) != null; ) {
bodyString.append(line);
}
return bodyString.toString();
}
}

View File

@ -1,6 +1,5 @@
package ink.wgink.module.wechat.pay.service.v3.jsapi.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.module.wechat.pay.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pay.pojo.v3.PaySign;
@ -9,6 +8,7 @@ import ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo.PayPrepay;
import ink.wgink.module.wechat.pay.remote.v3.IOrderPayRemoteService;
import ink.wgink.module.wechat.pay.service.BasePayService;
import ink.wgink.module.wechat.pay.service.v3.jsapi.IJsapiService;
import ink.wgink.module.wechat.pay.utils.v3.PayOrderUtil;
import ink.wgink.module.wechat.pay.utils.v3.PayPrivateKeyUtil;
import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.util.BeanPropertyCheckUtil;
@ -18,8 +18,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;
/**
* @ClassName: JsapiServiceImpl
* @Description: Jsapi业务
@ -47,7 +45,7 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
String serialNumber = getSerialNumber();
LOG.debug("调用微信支付,发起预支付");
JSONObject body = getPlaceOrderJsonObject(payPlaceOrder);
JSONObject body = PayOrderUtil.getPlaceOrderJsonObject(payPlaceOrder);
String authorization = getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
LOG.debug("请求获得结果");
@ -59,47 +57,13 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
PaySign paySign = new PaySign();
paySign.setAppId(miniappProperties.getAppKey());
paySign.setPrepayId(payPrepay.getPrepayId());
paySign.setPrepayId(payPrepay.getPrepay_id());
paySign.setNonceStr(nonceStr);
paySign.setTimestamp(String.valueOf(timestamp));
paySign.setPaySign(getPaySign(timestamp, nonceStr, payPrepay.getPrepayId()));
paySign.setPaySign(getPaySign(timestamp, nonceStr, payPrepay.getPrepay_id()));
return paySign;
}
/**
* 没问题会删除`
public PaySign placeOrder(PayPlaceOrder payPlaceOrder) throws Exception {
payPlaceOrder.setAppid(miniappProperties.getAppKey());
payPlaceOrder.setMchid(payProperties.getMchid());
BeanPropertyCheckUtil.checkField(payPlaceOrder);
String urlSuffix = "/v3/pay/transactions/jsapi";
String url = getPayBaseUrl() + urlSuffix;
LOG.debug("获得证书序列号");
String serialNumber = getSerialNumber();
LOG.debug("调用微信支付,发起预支付");
PlaceOrderPayRequestImpl placeOrderPayRequest = new PlaceOrderPayRequestImpl();
JSONObject body = getPlaceOrderJsonObject(payPlaceOrder);
String authorization = getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
LOG.debug("请求获得结果");
PayPrepay payPrepay = placeOrderPayRequest.post(url, authorization, serialNumber, body);
LOG.debug("生成返回结果");
long timestamp = System.currentTimeMillis() / 1000;
String nonceStr = WStringUtil.randomSubStr(UUIDUtil.get32UUID(), 10).toUpperCase();
PaySign paySign = new PaySign();
paySign.setAppId(miniappProperties.getAppKey());
paySign.setPrepayId(payPrepay.getPrepayId());
paySign.setNonceStr(nonceStr);
paySign.setTimestamp(String.valueOf(timestamp));
paySign.setPaySign(getPaySign(timestamp, nonceStr, payPrepay.getPrepayId()));
return paySign;
}
**/
/**
* 获得小程序签名
*
@ -110,147 +74,14 @@ public class JsapiServiceImpl extends BasePayService implements IJsapiService {
* @throws Exception
*/
private String getPaySign(long timestamp, String nonceStr, String prepayId) throws Exception {
StringBuilder signBS = new StringBuilder();
signBS.append(miniappProperties.getAppKey()).append("\n")
StringBuilder signSB = new StringBuilder();
signSB.append(miniappProperties.getAppKey()).append("\n")
.append(timestamp).append("\n")
.append(nonceStr).append("\n")
.append("prepay_id=").append(prepayId).append("\n");
String key = PayPrivateKeyUtil.getPrivateKey(payProperties.getKeyFilePath());
// 生成签名
return PayPrivateKeyUtil.encryptByPrivateKey(signBS.toString(), key);
}
/**
* 获取
* @param payPlaceOrder
* @return
*/
private JSONObject getPlaceOrderJsonObject(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()));
bodyObject.put("payer", getPayer(payPlaceOrder.getPayer()));
if (payPlaceOrder.getDetail() != null) {
bodyObject.put("detail", getDetail(payPlaceOrder.getDetail()));
}
if (payPlaceOrder.getSceneInfo() != null) {
bodyObject.put("scene_info", getSceneInfo(payPlaceOrder.getSceneInfo()));
}
if (payPlaceOrder.getSettleInfo() != null) {
bodyObject.put("settle_info", getSettleInfo(payPlaceOrder.getSettleInfo()));
}
return bodyObject;
}
/**
* 订单金额
*
* @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<PayPlaceOrder.Detail.Goods> 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;
return PayPrivateKeyUtil.encryptByPrivateKey(signSB.toString(), key);
}
}

View File

@ -0,0 +1,25 @@
package ink.wgink.module.wechat.pay.service.v3.nav;
import ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo.PayPlaceOrder;
/**
* @ClassName: INativeService
* @Description: native支付
* @Author: wanggeng
* @Date: 2022/11/10 17:15
* @Version: 1.0
*/
public interface INativeService {
/**
* 下单
*
* @param payPlaceOrder
* @return 此URL用于生成支付二维码然后提供给用户扫码支付<br/>
* 注意code_url并非固定值使用时按照URL格式转成二维码即可<br/>
* 示例值weixin://wxpay/bizpayurl/up?pr=NwY5Mz9&groupid=00
* @throws Exception
*/
String placeOrder(PayPlaceOrder payPlaceOrder) throws Exception;
}

View File

@ -0,0 +1,52 @@
package ink.wgink.module.wechat.pay.service.v3.nav.impl;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.module.wechat.pay.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo.PayPlaceOrder;
import ink.wgink.module.wechat.pay.pojo.v3.nav.PayCodeUrl;
import ink.wgink.module.wechat.pay.remote.v3.IOrderPayRemoteService;
import ink.wgink.module.wechat.pay.service.BasePayService;
import ink.wgink.module.wechat.pay.service.v3.nav.INativeService;
import ink.wgink.module.wechat.pay.utils.v3.PayOrderUtil;
import ink.wgink.properties.wechat.miniapp.MiniappProperties;
import ink.wgink.util.BeanPropertyCheckUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @ClassName: NativeServiceImpl
* @Description: native支付
* @Author: wanggeng
* @Date: 2022/11/10 17:53
* @Version: 1.0
*/
@Service
public class NativeServiceImpl extends BasePayService implements INativeService {
@Autowired
private MiniappProperties miniappProperties;
@Autowired
private IOrderPayRemoteService orderPayRemoteService;
@Override
public String placeOrder(PayPlaceOrder payPlaceOrder) throws Exception {
payPlaceOrder.setAppid(miniappProperties.getAppKey());
payPlaceOrder.setMchid(payProperties.getMchid());
BeanPropertyCheckUtil.checkField(payPlaceOrder);
String urlSuffix = "/v3/pay/transactions/native";
LOG.debug("获得证书序列号");
String serialNumber = getSerialNumber();
LOG.debug("调用微信支付,发起预支付");
JSONObject body = PayOrderUtil.getPlaceOrderJsonObject(payPlaceOrder);
String authorization = getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
LOG.debug("请求获得结果");
PayCodeUrl payCodeUrl = orderPayRemoteService.placeOrderNative(getPayBaseUrl(), authorization, serialNumber, body);
return payCodeUrl.getCode_url();
}
}

View File

@ -0,0 +1,21 @@
package ink.wgink.module.wechat.pay.service.v3.refund;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundNoticeCiphertext;
/**
* @ClassName: IRefundNoticeService
* @Description: 退款通知业务完成退款后微信支付发起异步通知实现该类完成业务处理
* @Author: wanggeng
* @Date: 2022/11/11 23:47
* @Version: 1.0
*/
public interface IRefundNoticeService {
/**
* 退款通知结果该方法在所有校验通过加密文本解密之后调用
*
* @param refundNoticeCiphertext
*/
void handle(RefundNoticeCiphertext refundNoticeCiphertext);
}

View File

@ -0,0 +1,56 @@
package ink.wgink.module.wechat.pay.service.v3.refund;
import ink.wgink.module.wechat.pay.pojo.v3.refund.Refund;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @ClassName: IRefundsService
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/10 21:29
* @Version: 1.0
*/
public interface IRefundService {
/**
* 退款
*
* @param transactionId
* @param refund
* @return
*/
RefundResult refund(String transactionId, Refund refund) throws Exception;
/**
* 业务订单退款
*
* @param businessOrderId
* @param refund
* @return
*/
RefundResult refundByBusinessOrder(String businessOrderId, Refund refund) throws Exception;
/**
* 详情
*
* @param businessRefundId
* @return
* @throws Exception
*/
RefundResult getByBusinessRefundId(String businessRefundId) throws Exception;
/**
* 通知
*
* @param request
* @param response
* @param nonce
* @param timestamp
* @param wechatpaySignature
*/
void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException;
}

View File

@ -0,0 +1,23 @@
package ink.wgink.module.wechat.pay.service.v3.refund.impl;
import ink.wgink.common.base.DefaultBaseService;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundNoticeCiphertext;
import ink.wgink.module.wechat.pay.service.v3.refund.IRefundNoticeService;
import org.springframework.stereotype.Service;
/**
* @ClassName: DefaultRefundNoticeServiceImpl
* @Description: 默认退款通知
* @Author: wanggeng
* @Date: 2022/11/12 00:36
* @Version: 1.0
*/
@Service
public class DefaultRefundNoticeServiceImpl extends DefaultBaseService implements IRefundNoticeService {
@Override
public void handle(RefundNoticeCiphertext refundNoticeCiphertext) {
LOG.debug("Pay notice: {}", refundNoticeCiphertext.toString());
}
}

View File

@ -0,0 +1,113 @@
package ink.wgink.module.wechat.pay.service.v3.refund.impl;
import com.alibaba.fastjson2.JSONObject;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.pay.enums.PayAuthorizationTypeEnum;
import ink.wgink.module.wechat.pay.enums.PayErrorMsgEnum;
import ink.wgink.module.wechat.pay.pojo.v3.Notice;
import ink.wgink.module.wechat.pay.pojo.v3.refund.Refund;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundNoticeCiphertext;
import ink.wgink.module.wechat.pay.pojo.v3.refund.RefundResult;
import ink.wgink.module.wechat.pay.remote.v3.IRefundsRemoteService;
import ink.wgink.module.wechat.pay.service.BasePayService;
import ink.wgink.module.wechat.pay.service.v3.refund.IRefundNoticeService;
import ink.wgink.module.wechat.pay.service.v3.refund.IRefundService;
import ink.wgink.module.wechat.pay.utils.v3.AsyncNoticeResultUtil;
import ink.wgink.properties.wechat.pay.v3.PayProperties;
import ink.wgink.util.map.HashMapUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
/**
* @ClassName: RefundsServiceImpl
* @Description: 退款
* @Author: wanggeng
* @Date: 2022/11/10 21:29
* @Version: 1.0
*/
@Service
public class RefundServiceImpl extends BasePayService implements IRefundService {
@Autowired
private PayProperties payProperties;
@Autowired
private IRefundsRemoteService refundsRemoteService;
@Autowired
private IRefundNoticeService refundNoticeService;
@Override
public RefundResult refund(String transactionId, Refund refund) throws Exception {
if (StringUtils.isBlank(transactionId)) {
throw new ParamsException("微信业务订单ID不能为空");
}
if (refund == null) {
throw new ParamsException("退款内容不能为空");
}
Map<String, Object> refundMap = HashMapUtil.beanToMap(refund);
refundMap.put("transaction_id", transactionId);
return refund(refundMap);
}
@Override
public RefundResult refundByBusinessOrder(String businessOrderId, Refund refund) throws Exception {
if (StringUtils.isBlank(businessOrderId)) {
throw new ParamsException("业务订单ID不能为空");
}
if (refund == null) {
throw new ParamsException("退款内容不能为空");
}
Map<String, Object> refundMap = HashMapUtil.beanToMap(refund);
refundMap.put("out_trade_no", businessOrderId);
return refund(refundMap);
}
@Override
public RefundResult getByBusinessRefundId(String businessRefundId) throws Exception {
String urlSuffix = "/v3/refund/domestic/refunds/" + businessRefundId;
String serialNumber = getSerialNumber();
String authorization = getAuthorization(RequestMethod.GET, urlSuffix, serialNumber, payProperties, null, PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
return refundsRemoteService.get(getPayBaseUrl(), authorization, serialNumber, businessRefundId);
}
@Override
public void notice(HttpServletRequest request, HttpServletResponse response, String nonce, String timestamp, String wechatpaySignature) throws IOException {
try {
Notice notice = AsyncNoticeResultUtil.getNotice(request, nonce, timestamp, wechatpaySignature);
String noticeCiphertextJsonString = AsyncNoticeResultUtil.getNoticeCiphertext(notice, payProperties.getApiV3Secretkey());
RefundNoticeCiphertext refundNoticeCiphertext = JSONObject.parseObject(noticeCiphertextJsonString, RefundNoticeCiphertext.class);
refundNoticeService.handle(refundNoticeCiphertext);
} catch (Exception e) {
LOG.error(e.getMessage(), e);
if (e instanceof SystemException) {
AsyncNoticeResultUtil.errorResult(response, PayErrorMsgEnum.SIGN_ERROR_401);
return;
}
AsyncNoticeResultUtil.errorResult(response, PayErrorMsgEnum.ERROR_500);
return;
}
AsyncNoticeResultUtil.successResult(response);
}
/**
* 退款
*
* @param refundMap
* @return
* @throws Exception
*/
private RefundResult refund(Map<String, Object> refundMap) throws Exception {
String urlSuffix = "/v3/refund/domestic/refunds";
String serialNumber = getSerialNumber();
JSONObject body = new JSONObject(refundMap);
String authorization = getAuthorization(RequestMethod.POST, urlSuffix, serialNumber, payProperties, body.toString(), PayAuthorizationTypeEnum.WECHATPAY2_SHA256_RSA2048);
return refundsRemoteService.refund(getPayBaseUrl(), authorization, serialNumber, body);
}
}

View File

@ -0,0 +1,118 @@
package ink.wgink.module.wechat.pay.utils.v3;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.exceptions.ParamsException;
import ink.wgink.exceptions.base.SystemException;
import ink.wgink.module.wechat.pay.enums.PayErrorMsgEnum;
import ink.wgink.module.wechat.pay.manager.v3.PayManager;
import ink.wgink.module.wechat.pay.pojo.v3.Notice;
import ink.wgink.module.wechat.pay.pojo.v3.PayErrorResponse;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
/**
* @ClassName: AsyncNoticeResultUtil
* @Description: 异步通知返回工具
* @Author: wanggeng
* @Date: 2022/11/11 23:27
* @Version: 1.0
*/
public class AsyncNoticeResultUtil {
private static final Logger LOG = LoggerFactory.getLogger(AsyncNoticeResultUtil.class);
/**
* 获取通知内容
*
* @param request
* @return
* @throws IOException
*/
public static Notice getNotice(HttpServletRequest request, String nonce, String timestamp, String wechatpaySignature) throws Exception {
String payNoticeJsonString = AsyncNoticeResultUtil.getBodyString(request);
if (StringUtils.isBlank(payNoticeJsonString)) {
throw new ParamsException("请求body为空");
}
LOG.debug("NoticeJsonString: {}", payNoticeJsonString);
Notice notice = JSONObject.parseObject(payNoticeJsonString, Notice.class);
LOG.debug("wechatpaySignature: {}", wechatpaySignature);
LOG.debug("验证签名");
boolean checkVerify = PayVerifyUtil.verifySignature(wechatpaySignature, payNoticeJsonString, nonce, timestamp, PayManager.getInstance().getPlatformCertificate());
if (!checkVerify) {
throw new SystemException("签名验证失败");
}
return notice;
}
/**
* 获取通知密文
*
* @param notice
* @param apiV3Secretkey
* @return
* @throws GeneralSecurityException
* @throws IOException
*/
public static String getNoticeCiphertext(Notice notice, String apiV3Secretkey) throws GeneralSecurityException, IOException {
LOG.debug("解密内容");
Notice.Resource resource = notice.getResource();
PayAesUtil aesUtil = new PayAesUtil(apiV3Secretkey.getBytes(StandardCharsets.UTF_8));
return aesUtil.decryptToString(resource.getAssociated_data().getBytes(StandardCharsets.UTF_8), resource.getNonce().getBytes(StandardCharsets.UTF_8), resource.getCiphertext());
}
/**
* 错误结果
*
* @param payErrorMsgEnum
*/
public static void errorResult(HttpServletResponse response, PayErrorMsgEnum payErrorMsgEnum) {
PayErrorResponse payErrorResponse = new PayErrorResponse();
payErrorResponse.setCode(payErrorMsgEnum.getErrorCode());
payErrorResponse.setMessage(payErrorMsgEnum.getSummary());
response.setContentType("application/json; charset=UTF-8");
response.setStatus(payErrorMsgEnum.getCode());
try (PrintWriter writer = response.getWriter()) {
writer.println(JSONObject.toJSONString(payErrorResponse));
writer.flush();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
/**
* 成功结果
*/
public static void successResult(HttpServletResponse response) {
response.setContentType("application/json; charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
JSONObject successJsonObject = new JSONObject();
successJsonObject.put("code", "SUCCESS");
successJsonObject.put("message", "成功");
try (PrintWriter writer = response.getWriter()) {
writer.println(successJsonObject.toJSONString());
writer.flush();
} catch (IOException e) {
LOG.error(e.getMessage(), e);
}
}
public static String getBodyString(HttpServletRequest request) throws IOException {
BufferedReader reader = request.getReader();
StringBuilder bodyString = new StringBuilder();
for (String line; (line = reader.readLine()) != null; ) {
bodyString.append(line);
}
return bodyString.toString();
}
}

View File

@ -0,0 +1,153 @@
package ink.wgink.module.wechat.pay.utils.v3;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import ink.wgink.module.wechat.pay.pojo.v3.jsapi.pojo.PayPlaceOrder;
import java.util.List;
/**
* @ClassName: PayOrderUtil
* @Description: 订单工具类
* @Author: wanggeng
* @Date: 2022/11/10 18:33
* @Version: 1.0
*/
public class PayOrderUtil {
/**
* 获取
*
* @param payPlaceOrder
* @return
*/
public static JSONObject getPlaceOrderJsonObject(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("support_fapiao", payPlaceOrder.getSupportFapiao());
bodyObject.put("amount", getAmountJsonObject(payPlaceOrder.getAmount()));
bodyObject.put("payer", getPayer(payPlaceOrder.getPayer()));
if (payPlaceOrder.getDetail() != null) {
bodyObject.put("detail", getDetail(payPlaceOrder.getDetail()));
}
if (payPlaceOrder.getSceneInfo() != null) {
bodyObject.put("scene_info", getSceneInfo(payPlaceOrder.getSceneInfo()));
}
if (payPlaceOrder.getSettleInfo() != null) {
bodyObject.put("settle_info", getSettleInfo(payPlaceOrder.getSettleInfo()));
}
return bodyObject;
}
/**
* 订单金额
*
* @param amount
* @return
*/
private static 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 static JSONObject getPayer(PayPlaceOrder.Payer payer) {
JSONObject payerJsonObject = new JSONObject();
payerJsonObject.put("openid", payer.getOpenid());
return payerJsonObject;
}
/**
* 优惠功能
*
* @param detail
* @return
*/
private static 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 static JSONArray listGoodsDetail(List<PayPlaceOrder.Detail.Goods> 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 static 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 static 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 static JSONObject getSettleInfo(PayPlaceOrder.SettleInfo settleInfo) {
JSONObject settleJsonObject = new JSONObject();
settleJsonObject.put("profit_sharing", settleInfo.getProfitSharing());
return settleJsonObject;
}
}