package com.ghy.payment.service; import com.alibaba.fastjson.JSONObject; import com.ghy.common.adapay.AdapayConfig; import com.ghy.common.adapay.AdapayProperties; import com.ghy.common.adapay.model.*; import com.ghy.payment.service.impl.DrawCashCallbackService; import com.ghy.payment.service.impl.PayCallbackService; import com.ghy.payment.service.impl.PayReverseCallbackService; import com.ghy.payment.service.impl.RefundCallbackService; import com.huifu.adapay.core.exception.BaseAdaPayException; import com.huifu.adapay.model.*; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import javax.validation.constraints.NotNull; import java.util.Collection; import java.util.List; /** * @author HH 2022/3/25 */ @Service public class AdapayService { private static final Logger logger = LoggerFactory.getLogger(AdapayService.class); @Resource private PayCallbackService payCallbackService; @Resource private RefundCallbackService refundCallbackService; @Resource private DrawCashCallbackService drawCashCallbackService; @Resource private PayReverseCallbackService payReverseCallbackService; @Resource private AdapayProperties adapayProperties; /** * 支付确认 * 适用于延时分账的场景。只有已支付完成且延时分账的Payment对象,才支持调用创建支付确认对象。 * 支持一次全额或多次部分确认,多次部分确认时,当前确认金额 + 已确认金额 + 已撤销金额不能大于原支付金额。 * * @param deptId [必填]商户ID * @param paymentId [必填] String(64) Adapay生成的支付对象id * @param orderNo [必填] String(64) 请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param confirmAmt [必填] String(14) 确认金额,必须大于0,保留两位小数点,如0.10、100.05等。必须小于等于原支付金额-已确认金额-已撤销金额 * @return 成功时同步返回一个包含 支付确认对象的JSON https://docs.adapay.tech/api/trade.html#id54 */ public JSONObject paymentConfirm(@NotNull Long deptId, @NotNull String paymentId, @NotNull String orderNo, @NotNull String confirmAmt) throws BaseAdaPayException { return paymentConfirm(deptId, paymentId, orderNo, confirmAmt, null, null, null); } /** * 支付确认 * 适用于延时分账的场景。只有已支付完成且延时分账的Payment对象,才支持调用创建支付确认对象。 * 支持一次全额或多次部分确认,多次部分确认时,当前确认金额 + 已确认金额 + 已撤销金额不能大于原支付金额。 * * @param deptId [必填]商户ID * @param paymentId [必填] String(64) Adapay生成的支付对象id * @param orderNo [必填] String(64) 请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param confirmAmt [必填] String(14) 确认金额,必须大于0,保留两位小数点,如0.10、100.05等。必须小于等于原支付金额-已确认金额-已撤销金额 * @param divMembers 分账对象信息列表,一次请求最多仅支持7个分账方。json对象 形式,详见 分账对象信息列表 * @param feeMode String(1) 手续费收取模式:O-商户手续费账户扣取手续费,I-交易金额中扣取手续费;值为空时,默认值为I;若为O时,分账对象列表中不支持传入手续费承担方 * @param description String(128) 附加说明 * @return 成功时同步返回一个包含 支付确认对象的JSON https://docs.adapay.tech/api/trade.html#id54 */ public JSONObject paymentConfirm(@NotNull Long deptId, @NotNull String paymentId, @NotNull String orderNo, @NotNull String confirmAmt, List divMembers, String feeMode, String description) throws BaseAdaPayException { JSONObject confirmParams = new JSONObject(); confirmParams.put("payment_id", paymentId); confirmParams.put("order_no", orderNo); confirmParams.put("confirm_amt", confirmAmt); confirmParams.put("div_members", divMembers); confirmParams.put("fee_mode", feeMode); confirmParams.put("description", description); return (JSONObject) PaymentConfirm.create(confirmParams, deptId.toString()); } /** * 创建余额支付请求 * 商户利用该接口进行余额支付,支持同一商户下的商户-用户,用户-商户,用户-用户间的账户余额支付 * * @param deptId [必填]商户ID * @param orderNo [必填]请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param outMemberId [必填]出账用户的member_id, 若为商户本身时,请传入0 * @param inMemberId [必填]入账用户的member_id, 若为商户本身时,请传入0 * @param transAmt [必填]交易金额,必须大于0,人民币为元,保留两位小数点,如"0.10"、"100.05"等 * @param goodsTitle [必填]商品名称 * @param goodsDesc [必填]商品描述 * @param payMode 支付模式,delay- 延时分账模式(值为 delay 时,div_members 字段必须为空); * 值为空时并且div_mermbers不为空时,表示实时分账; * 值为空时并且div_mermbers也为空时,表示不分账; * @param divMembers 分账对象信息列表,最多仅支持7个分账方 * @return 成功时同步返回交易结果的 JSON */ public JSONObject balancePay(@NotNull Long deptId, @NotNull String orderNo, @NotNull String outMemberId, @NotNull String inMemberId, @NotNull String transAmt, @NotNull String goodsTitle, @NotNull String goodsDesc, String payMode, Collection divMembers) throws BaseAdaPayException { // 获取商户的appId String appId = AdapayConfig.getAppId(deptId); JSONObject balanceParam = new JSONObject(); balanceParam.put("app_id", appId); balanceParam.put("adapay_func_code", "settle_accounts.balancePay"); balanceParam.put("order_no", orderNo); balanceParam.put("out_member_id", outMemberId); balanceParam.put("in_member_id", inMemberId); balanceParam.put("trans_amt", transAmt); balanceParam.put("goods_title", goodsTitle); balanceParam.put("goods_desc", goodsDesc); balanceParam.put("pay_mode", payMode); if (!CollectionUtils.isEmpty(divMembers)) { balanceParam.put("div_members", divMembers); } return (JSONObject) AdapayCommon.requestAdapay(balanceParam, deptId.toString()); } /** * 创建结算账户对象 https://docs.adapay.tech/api/trade.html#settle-account-create * 创建结算账户对象是为一个已创建用户对象创建结算账户,用于对用户分账金额的结算,目前仅支持绑定银行卡结算账户。 * 用户创建对私结算账户时,会对银行卡号、银行卡开户姓名、身份证号三要素认证,若认证失败,则创建结算账户失败。 * 每个结算账户对象 Adapay 系统会生成一个唯一的 id,可用于查询结算账户对象,或者删除结算账户对象。 * * @param deptId [必填]商户ID * @param memberId [必填]商户下的用户id,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param cardId [必填]银行卡号 * @param cardName [必填]银行卡对应的户名 * @param telNo [必填]手机号 * @param bankAcctType [必填]银行账户类型:1-对公;2-对私 * @param certId 证件号,银行账户类型为对私时必填 * @param bankCode 银行编码,银行账户类型对公时必填,详见附录 银行代码 https://docs.adapay.tech/api/appendix.html#id3 * @param provCode 银行账户开户银行所在省份编码 (省市编码),银行账户类型为对公时必填,省市编码详见area.json * @param areaCode 银行账户开户银行所在地区编码(省市编码),银行账户类型为对公时必填,省市编码详见area.json * @return 成功时同步返回一个包含 SettleAccount对象 的 JSON。 */ public JSONObject createSettleAccount(@NotNull Long deptId, @NotNull String memberId, @NotNull String cardId, @NotNull String cardName, @NotNull String bankAcctType, String certId, String telNo, String bankCode, String provCode, String areaCode) throws BaseAdaPayException { // 获取商户的appId String appId = AdapayConfig.getAppId(deptId); // 结算账户信息 参见结算账户信息(AccountInfo)对象 https://docs.adapay.tech/api/appendix.html#accountinfo JSONObject accountInfo = new JSONObject(); switch (bankAcctType) { case "1": Assert.isTrue(StringUtils.isNoneBlank(bankCode, provCode, areaCode), "[bankCode, provCode, areaCode] cannot be empty !"); break; case "2": Assert.hasText(cardId, "cardId is blank !"); accountInfo.put("cert_type", "00"); accountInfo.put("cert_id", certId); break; default: throw new BaseAdaPayException("Wrong bankAcctType !"); } accountInfo.put("card_id", cardId); accountInfo.put("card_name", cardName); accountInfo.put("tel_no", telNo); accountInfo.put("bank_code", bankCode); accountInfo.put("bank_acct_type", bankAcctType); accountInfo.put("prov_code", provCode); accountInfo.put("area_code", areaCode); JSONObject settleCountParams = new JSONObject(); settleCountParams.put("member_id", memberId); settleCountParams.put("app_id", appId); // 目前仅支持:bank_account(银行卡) settleCountParams.put("channel", "bank_account"); settleCountParams.put("account_info", accountInfo); return (JSONObject) SettleAccount.create(settleCountParams, deptId.toString()); } /** * 创建实名用户 https://docs.adapay.tech/api/trade.html#member-realname * * @param deptId [必填]商户ID * @param memberId [必填]商户下的用户id,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param telNo [必填]用户手机号 * @param username [必填]用户姓名 * @param certId [必填]证件号 * @return 成功时同步返回一个包含Member对象的JSON */ public JSONObject createMember(@NotNull Long deptId, @NotNull String memberId, @NotNull String telNo, @NotNull String username, @NotNull String certId) throws BaseAdaPayException { // 获取商户的appId String appId = AdapayConfig.getAppId(deptId); JSONObject memberParams = new JSONObject(); memberParams.put("member_id", memberId); memberParams.put("app_id", appId); memberParams.put("tel_no", telNo); memberParams.put("user_name", username); // 证件类型,仅支持:00-身份证 memberParams.put("cert_type", "00"); // 接口功能号 memberParams.put("adapay_func_code", "members.realname"); memberParams.put("cert_id", certId); return (JSONObject) AdapayCommon.requestAdapay(memberParams, deptId.toString()); } /** * 对指定商户或者商户下用户的结算账户可用余额发起主动取现操作,金额从账户中提到绑定的结算银行卡中 * * @param deptId [必填]商户ID * @param orderNo [必填项]请求订单号,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @param cashType [必填项]取现类型:T1-T+1取现;D1-D+1取现;D0-即时取现。 * @param cashAmt [必填项]取现金额,必须大于0,人民币为元,保留两位小数点,如"0.10"、"100.05"等 * @param memberId [必填项]用户对象的member_id,若是商户本身取现时,请传入0 * @param remark 备注 * @param feeMode 手续费收取模式:O-商户手续费账户扣取手续费,I-交易金额中扣取手续费;值为空时,默认值为I; * @return https://docs.adapay.tech/api/wallet.html#cash-response */ public JSONObject drawCash(@NotNull Long deptId, String orderNo, String cashType, String cashAmt, String memberId, String remark, String feeMode) throws BaseAdaPayException { // 获取商户的appId String appId = AdapayConfig.getAppId(deptId); Assert.hasText(orderNo, "orderNo is blank!"); Assert.hasText(cashType, "cashType is blank!"); Assert.hasText(cashAmt, "cashAmt is blank!"); Assert.hasText(memberId, "memberId is blank!"); JSONObject cashParam = new JSONObject(); cashParam.put("order_no", orderNo); cashParam.put("app_id", appId); cashParam.put("cash_type", cashType); cashParam.put("cash_amt", cashAmt); cashParam.put("member_id", memberId); cashParam.put("notify_url", adapayProperties.getNotifyUrl()); cashParam.put("remark", remark); cashParam.put("fee_mode", feeMode); JSONObject response = (JSONObject) Drawcash.create(cashParam, deptId.toString()); drawCashCallbackService.onResponse(response); return response; } /** * 支付宝正扫支付 */ public JSONObject alipayQrPay(@NotNull Long deptId, PayParam payParam, AlipayExpend expend, DeviceInfo deviceInfo, Collection divMembers) throws BaseAdaPayException { return pay(deptId, "alipay_qr", payParam, expend, deviceInfo, divMembers); } /** * 微信公众号支付 */ public JSONObject wxPubPay(@NotNull Long deptId, PayParam payParam, WxpayExpend expend, DeviceInfo deviceInfo, Collection divMembers) throws BaseAdaPayException { return pay(deptId, "wx_pub", payParam, expend, deviceInfo, divMembers); } /** * 微信小程序支付 */ public JSONObject wxLitePay(@NotNull Long deptId, PayParam payParam, WxpayExpend expend, DeviceInfo deviceInfo, Collection divMembers) throws BaseAdaPayException { return pay(deptId, "wx_lite", payParam, expend, deviceInfo, divMembers); } /** * 聚合支付 * * @param deptId [必填]商户ID * @param payChannel [必填项]支付渠道,详见 https://docs.adapay.tech/api/appendix.html#id2 * @param payParam [必填项]支付参数 * @param expend 支付渠道额外参数 https://docs.adapay.tech/api/appendix.html#expend * @param deviceInfo 前端设备信息 https://docs.adapay.tech/api/appendix.html#deviceinfo * @param divMembers 分账对象信息列表 https://docs.adapay.tech/api/appendix.html#divmembers * @return 同步返回一个 支付对象,详见 https://docs.adapay.tech/api/trade.html#id2 */ public JSONObject pay(@NotNull Long deptId, @NotNull String payChannel, @NotNull PayParam payParam, Expend expend, DeviceInfo deviceInfo, Collection divMembers) throws BaseAdaPayException { // 获取商户的appId String appId = AdapayConfig.getAppId(deptId); JSONObject paymentParams = payParam.toJSONObject(); paymentParams.put("app_id", appId); paymentParams.put("notify_url", adapayProperties.getNotifyUrl()); paymentParams.put("pay_channel", payChannel); paymentParams.put("div_members", divMembers); paymentParams.put("device_info", deviceInfo); paymentParams.put("expend", expend); logger.debug("paymentParams: {}", paymentParams.toJSONString()); JSONObject response = (JSONObject) Payment.create(paymentParams, deptId.toString()); payCallbackService.onResponse(response); return response; } /** * 发起退款(用于[延迟分账]或者[延迟分账且确认支付后]的支付单) * 当您的业务需要发起退款时,可通过 Adapay 系统提供的创建 Refund对象 方法创建一个退款对象,资金会原路退回用户的支付宝或微信中。 * 支持一次全额或多次部分退款,退款次数最多不超过10次。多次部分退款时,当前退款金额 + 已退款金额不能大于原支付金额。 * 对于每次撤销交易,Adapay 都会通过 异步消息通知 告知结果。 * 退款对象同步返回成功,表示Adapay受理成功,退款结果以异步通知为准。支持退款最长时间为178天, * 若返回码是“order_id_not_exists 订单记录不存在”,既超过退款期限,无法退款成功。 * * @param deptId [必填]商户ID * @param paymentId [必填项]支付确认对象的id * @param refundOrderNo [必填项]订单号 * @param refundAmt [必填项]退款金额,若退款金额小于原交易金额,则认为是部分退款,必须大于0,保留两位小数点,如0.10、100.05等 * @return 同步返回一个 退款对象 https://docs.adapay.tech/api/trade.html#create-refund-params */ public JSONObject refund(@NotNull Long deptId, @NotNull String paymentId, @NotNull String refundOrderNo, @NotNull String refundAmt) throws BaseAdaPayException { Assert.hasText(paymentId, "paymentId is blank!"); Assert.hasText(refundOrderNo, "refundOrderNo is blank!"); Assert.hasText(refundAmt, "refundAmt is blank!"); JSONObject refundParams = new JSONObject(); refundParams.put("refund_amt", refundAmt); refundParams.put("refund_order_no", refundOrderNo); JSONObject response = (JSONObject) Refund.create(paymentId, refundParams, deptId.toString()); refundCallbackService.onResponse(response); return response; } /** * 支付关单 * 针对已经创建的 支付对象,您可以调用关单接口进行交易的关闭。调用此接口后,该用户订单将不再能支付成功。 对于关单功能的使用有如下规则: * 1.存在关单记录,不能再次关单 * 2.交易时间 1分钟 内无法关单成功 * 3.正扫交易时间超过 2小时 无法关单成功 * 4.支付宝正扫接口,如果用户没有扫码,订单不能关闭成功(二维码给用户展示,如果用户没有用手机去扫码,那这笔就不能关单; 如果用户扫过了的话(无需支付成功)就可以关单了)—-微信正扫无此条限制 * 5.网银和快捷类交易都不支持关单操作 * * @param deptId [必填]商户ID * @param paymentId [必填项]由 Adapay 生成的支付对象 id * @param reason 关单描述 * @param expend 扩展域 * @return 关单的结果将通过一个 JSON 同步返回 https://docs.adapay.tech/api/trade.html#close-payment-response */ public JSONObject close(@NotNull Long deptId, String paymentId, String reason, String expend) throws BaseAdaPayException { Assert.hasText(paymentId, "paymentId is blank!"); JSONObject paymentParams = new JSONObject(); paymentParams.put("payment_id", paymentId); paymentParams.put("reason", reason); paymentParams.put("expend", expend); return (JSONObject) Payment.close(paymentParams, deptId.toString()); } /** * 查询已创建的单个用户对象 * https://docs.adapay.tech/api/trade.html#id41 * * @param deptId [必填]商户ID * @param memberId [必填]商户下的用户id,只能为英文、数字或者下划线的一种或多种组合,保证在app_id下唯一 * @return 成功时同步返回一个包含 Member对象的JSON。 */ public JSONObject queryMember(@NotNull Long deptId, @NotNull String memberId) throws BaseAdaPayException { JSONObject memberParams = new JSONObject(); memberParams.put("member_id", memberId); memberParams.put("app_id", AdapayConfig.getAppId(deptId)); return (JSONObject) Member.query(memberParams, deptId.toString()); } /** * 支付撤销 * 支付撤销对象适用于[延时分账]的场景。只有已支付完成且为延时分账的Payment对象,在没有创建支付确认对象成功之前,可以调用创建支付撤销对象。 * 用来撤销支付,资金会原路退回用户的支付宝或微信中。 支持一次全额或多次部分撤销,撤销次数最多不超过10次。 * 多次部分撤销时,当前撤销金额 + 已撤销金额 + 已确认金额不能大于原支付金额。 * 对于每次撤销交易,Adapay 都会通过 异步消息通知 告知结果。 * https://docs.adapay.tech/api/trade.html#payment-reverse-object * * @param deptId [必填]商户ID * @param paymentId [必填]Adapay生成的支付对象id * @param reverseAmt [必填]撤销金额,必须大于0,保留两位小数点,如0.10、100.05等。撤销金额必须小于等于支付金额 - 已确认金额 - 已撤销(撤销成功+撤销中)金额。 * @return 创建支付撤销对象同步返回成功,表示 Adapay 受理成功,撤销结果以异步通知为准 */ public JSONObject payReverse(@NotNull Long deptId, @NotNull String paymentId, @NotNull String reverseAmt) throws BaseAdaPayException { JSONObject reverseParams = new JSONObject(); reverseParams.put("app_id", AdapayConfig.getAppId(deptId)); reverseParams.put("payment_id", paymentId); reverseParams.put("reverse_amt", reverseAmt); reverseParams.put("notify_url", adapayProperties.getNotifyUrl()); reverseParams.put("order_no", "PAYMENT_REVERSE" + System.currentTimeMillis()); JSONObject response = (JSONObject) PaymentReverse.create(reverseParams, deptId.toString()); payReverseCallbackService.onResponse(response); return response; } }