package com.supwisdom.dlpay.payapi.model;
import java.util.Objects;
+
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.supwisdom.dlpay.payapi.model.TransResult;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.openapitools.jackson.nullable.JsonNullable;
+
import javax.validation.Valid;
import javax.validation.constraints.*;
*/
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-03-17T08:49:02.541+08:00[Asia/Shanghai]")
-public class QrcodePayConfirmResponse {
+public class QrcodePayConfirmResponse {
@JsonProperty("refno")
private String refno;
@JsonProperty("result")
private TransResult result;
+ @JsonProperty("amount")
+ private Integer amount;
+
public QrcodePayConfirmResponse refno(String refno) {
this.refno = refno;
return this;
/**
* Get refno
+ *
* @return refno
- */
+ */
@ApiModelProperty(value = "")
-@Pattern(regexp="[0-9]*") @Size(min=16)
+ @Pattern(regexp = "[0-9]*")
+ @Size(min = 16)
public String getRefno() {
return refno;
}
/**
* Get hostDate
+ *
* @return hostDate
- */
+ */
@ApiModelProperty(value = "")
-@Pattern(regexp="[0-9]*") @Size(min=8,max=8)
+ @Pattern(regexp = "[0-9]*")
+ @Size(min = 8, max = 8)
public String getHostDate() {
return hostDate;
}
/**
* Get hostTime
+ *
* @return hostTime
- */
+ */
@ApiModelProperty(value = "")
-@Pattern(regexp="[0-9]*") @Size(min=6,max=6)
+ @Pattern(regexp = "[0-9]*")
+ @Size(min = 6, max = 6)
public String getHostTime() {
return hostTime;
}
/**
* Get description
+ *
* @return description
- */
+ */
@ApiModelProperty(value = "")
/**
* Get result
+ *
* @return result
- */
+ */
@ApiModelProperty(value = "")
@Valid
this.result = result;
}
+ public QrcodePayConfirmResponse amount(Integer amount) {
+ this.amount = amount;
+ return this;
+ }
+
+ /**
+ * Get amount
+ * @return amount
+ */
+ @ApiModelProperty(value = "")
+
+
+ public Integer getAmount() {
+ return amount;
+ }
+
+ public void setAmount(Integer amount) {
+ this.amount = amount;
+ }
+
@Override
public boolean equals(java.lang.Object o) {
Objects.equals(this.hostDate, qrcodePayConfirmResponse.hostDate) &&
Objects.equals(this.hostTime, qrcodePayConfirmResponse.hostTime) &&
Objects.equals(this.description, qrcodePayConfirmResponse.description) &&
- Objects.equals(this.result, qrcodePayConfirmResponse.result);
+ Objects.equals(this.result, qrcodePayConfirmResponse.result) &&
+ Objects.equals(this.amount, qrcodePayConfirmResponse.amount);
}
@Override
public int hashCode() {
- return Objects.hash(refno, hostDate, hostTime, description, result);
+ return Objects.hash(refno, hostDate, hostTime, description, result, amount);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class QrcodePayConfirmResponse {\n");
-
+
sb.append(" refno: ").append(toIndentedString(refno)).append("\n");
sb.append(" hostDate: ").append(toIndentedString(hostDate)).append("\n");
sb.append(" hostTime: ").append(toIndentedString(hostTime)).append("\n");
sb.append(" description: ").append(toIndentedString(description)).append("\n");
sb.append(" result: ").append(toIndentedString(result)).append("\n");
+ sb.append(" amount: ").append(toIndentedString(amount)).append("\n");
sb.append("}");
return sb.toString();
}
package com.supwisdom.dlpay.payapi.model;
-import java.util.Objects;
import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
-import org.openapitools.jackson.nullable.JsonNullable;
-import javax.validation.Valid;
-import javax.validation.constraints.*;
+
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Pattern;
+import javax.validation.constraints.Size;
+import java.util.Objects;
/**
* RefundRequest
@JsonProperty("amount")
private Integer amount;
+ @JsonProperty("shopaccno")
+ private String shopaccno;
+
public RefundRequest billno(String billno) {
this.billno = billno;
return this;
@ApiModelProperty(required = true, value = "")
@NotNull
-@Pattern(regexp="[0-9]*") @Size(min=16)
+@Pattern(regexp="[0-9]*") @Size(min=16)
public String getBillno() {
return billno;
}
@ApiModelProperty(required = true, value = "")
@NotNull
-@Pattern(regexp="[0-9]*") @Size(min=8,max=8)
+@Pattern(regexp="[0-9]*") @Size(min=8,max=8)
public String getTransDate() {
return transDate;
}
@ApiModelProperty(required = true, value = "")
@NotNull
-@Pattern(regexp="[0-9]*") @Size(min=6,max=6)
+@Pattern(regexp="[0-9]*") @Size(min=6,max=6)
public String getTransTime() {
return transTime;
}
@ApiModelProperty(required = true, value = "")
@NotNull
-@Pattern(regexp="[0-9]*") @Size(min=16)
+@Pattern(regexp="[0-9]*") @Size(min=16)
public String getRefno() {
return refno;
}
this.amount = amount;
}
+ public RefundRequest shopaccno(String shopaccno) {
+ this.shopaccno = shopaccno;
+ return this;
+ }
+
+ /**
+ * Get shopaccno
+ * @return shopaccno
+ */
+ @ApiModelProperty(required = true, value = "")
+ @NotNull
+
+ @Pattern(regexp="[0-9]*")
+ public String getShopaccno() {
+ return shopaccno;
+ }
+
+ public void setShopaccno(String shopaccno) {
+ this.shopaccno = shopaccno;
+ }
@Override
public boolean equals(java.lang.Object o) {
Objects.equals(this.transDate, refundRequest.transDate) &&
Objects.equals(this.transTime, refundRequest.transTime) &&
Objects.equals(this.refno, refundRequest.refno) &&
- Objects.equals(this.amount, refundRequest.amount);
+ Objects.equals(this.amount, refundRequest.amount) &&
+ Objects.equals(this.shopaccno, refundRequest.shopaccno);
}
@Override
public int hashCode() {
- return Objects.hash(billno, transDate, transTime, refno, amount);
+ return Objects.hash(billno, transDate, transTime, refno, amount, shopaccno);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("class RefundRequest {\n");
-
+
sb.append(" billno: ").append(toIndentedString(billno)).append("\n");
sb.append(" transDate: ").append(toIndentedString(transDate)).append("\n");
sb.append(" transTime: ").append(toIndentedString(transTime)).append("\n");
sb.append(" refno: ").append(toIndentedString(refno)).append("\n");
sb.append(" amount: ").append(toIndentedString(amount)).append("\n");
+ sb.append(" shopaccno: ").append(toIndentedString(shopaccno)).append("\n");
sb.append("}");
return sb.toString();
}
result:
title: 交易结果
$ref: 'definitions.yaml#/components/schemas/TransResult'
+ amount:
+ type: integer
+ format: int32
+ title: 消费金额
RefundRequest:
type: object
title: 退款申请
- transTime
- refno
- amount
+ - shopaccno
properties:
billno:
title: 退款申请订单号
refno:
title: 退款申请原始订单交易参考号
$ref: 'definitions.yaml#/components/schemas/Refno'
+ shopaccno:
+ $ref: 'definitions.yaml#/components/schemas/ShopAccNo'
amount:
type: integer
title: 退款金额, 正式
*/
package com.supwisdom.dlpay.api;
-import com.supwisdom.dlpay.payapi.model.ErrorResponse;
-import com.supwisdom.dlpay.payapi.model.QrcodePayConfirmRequest;
-import com.supwisdom.dlpay.payapi.model.QrcodePayConfirmResponse;
-import com.supwisdom.dlpay.payapi.model.QrcodePayInitRequest;
-import com.supwisdom.dlpay.payapi.model.QrcodePayInitResponse;
-import com.supwisdom.dlpay.payapi.model.RefundRequest;
-import com.supwisdom.dlpay.payapi.model.RefundResponse;
+import com.supwisdom.dlpay.payapi.model.*;
import io.swagger.annotations.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestHeader;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.NativeWebRequest;
-import org.springframework.web.multipart.MultipartFile;
import javax.validation.Valid;
-import javax.validation.constraints.*;
-import java.util.List;
-import java.util.Map;
import java.util.Optional;
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-03-17T08:49:02.541+08:00[Asia/Shanghai]")
--- /dev/null
+package com.supwisdom.dlpay.api.dao;
+
+import com.supwisdom.dlpay.api.domain.TRefundDtl;
+import org.springframework.data.repository.CrudRepository;
+
+public interface RefundDtlDao extends CrudRepository<TRefundDtl,String> {
+ TRefundDtl findFirstByOriginrefnoAndTenantidOrderBySeqnoDesc(String originrefno, String tenantid);
+}
import javax.persistence.LockModeType;
import javax.persistence.QueryHint;
+import java.util.List;
public interface TransactionMainDao extends CrudRepository<TTransactionMain, String> {
@Query("select min(t.accdate) from TTransactionMain t where t.sourceType=?1 ")
String findMinAccdateBySourcetype(String sourcetype);
+
+ List<TTransactionMain> findByReverseRefno(String refno);
}
@Column(name = "ACCNO", length = 32)
private String accountNo;
- @Column(name = "accname", length = 32)
- private String accountName; // 账户名
-
@Column(name = "USERNAME", length = 200)
private String userName;
public void setAftbal(Double aftbal) {
this.aftbal = aftbal;
}
-
- public String getAccountName() {
- return accountName;
- }
-
- public void setAccountName(String accountName) {
- this.accountName = accountName;
- }
}
--- /dev/null
+package com.supwisdom.dlpay.api.domain;
+
+import javax.persistence.*;
+import javax.validation.constraints.NotNull;
+
+@Entity
+@Table(name = "TB_REFUNDDTL",
+ indexes = {@Index(name = "refunddtl_idx",columnList = "originrefno,seqno,tenantid",unique = true),
+ @Index(name = "refunddtl_idx2",columnList = "outid,outtradeno,tenantid",unique = true)})
+public class TRefundDtl {
+ /**
+ * refno 退款流水交易参考号
+ */
+ @Id
+ @Column(name = "refno",length = 32)
+ private String refno;
+
+ /**
+ * originrefno 交易主表流水号
+ */
+ @NotNull
+ @Column(name = "originrefno",length = 32)
+ private String originrefno;
+
+ @NotNull
+ @Column(name = "amount",precision = 9,scale = 2)
+ private Double amount;
+
+ /**
+ * afteramount 原流水退款剩余可退金额
+ */
+ @NotNull
+ @Column(name = "afteramount",precision = 9,scale = 2)
+ private Double afteramount;
+
+ /**
+ * 商户号
+ */
+ @Column(name = "outid",length = 60)
+ private String outid;
+
+ /**
+ * 退款商户billno
+ */
+ @Column(name = "outtradeno",length = 60)
+ private String outtradeno;
+
+ @Column(name = "seqno")
+ private Integer seqno;
+
+ @NotNull
+ @Column(name = "tenantid",length = 20)
+ private String tenantid;
+
+ @NotNull
+ @Column(name = "tradeflag", length = 10)
+ private String tradeflag;
+
+ public String getRefno() {
+ return refno;
+ }
+
+ public void setRefno(String refno) {
+ this.refno = refno;
+ }
+
+ public String getOriginrefno() {
+ return originrefno;
+ }
+
+ public void setOriginrefno(String originrefno) {
+ this.originrefno = originrefno;
+ }
+
+ public Double getAmount() {
+ return amount;
+ }
+
+ public void setAmount(Double amount) {
+ this.amount = amount;
+ }
+
+ public Double getAfteramount() {
+ return afteramount;
+ }
+
+ public void setAfteramount(Double afteramount) {
+ this.afteramount = afteramount;
+ }
+
+ public String getOutid() {
+ return outid;
+ }
+
+ public void setOutid(String outid) {
+ this.outid = outid;
+ }
+
+ public String getOuttradeno() {
+ return outtradeno;
+ }
+
+ public void setOuttradeno(String outtradeno) {
+ this.outtradeno = outtradeno;
+ }
+
+ public Integer getSeqno() {
+ return seqno;
+ }
+
+ public void setSeqno(Integer seqno) {
+ this.seqno = seqno;
+ }
+
+ public String getTenantid() {
+ return tenantid;
+ }
+
+ public void setTenantid(String tenantid) {
+ this.tenantid = tenantid;
+ }
+
+ public String getTradeflag() {
+ return tradeflag;
+ }
+
+ public void setTradeflag(String tradeflag) {
+ this.tradeflag = tradeflag;
+ }
+}
import com.supwisdom.dlpay.api.service.ConsumePayService
import com.supwisdom.dlpay.api.service.SourceTypeService
import com.supwisdom.dlpay.api.service.TransactionServiceProxy
+import com.supwisdom.dlpay.exception.InternalServerError
import com.supwisdom.dlpay.framework.util.*
import com.supwisdom.dlpay.framework.util.RandomUtils.getRandomString
import com.supwisdom.dlpay.util.PaytypeUtil
import mu.KotlinLogging
import org.apache.commons.lang.StringUtils
+import org.apache.http.client.methods.HttpPost
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory
+import org.apache.http.entity.StringEntity
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.ssl.SSLContexts
+import org.apache.http.util.EntityUtils
import org.springframework.http.converter.StringHttpMessageConverter
import org.springframework.stereotype.Service
import org.springframework.web.client.RestTemplate
+import java.io.File
+import java.io.FileInputStream
import java.nio.charset.StandardCharsets
+import java.security.KeyStore
+import kotlin.math.abs
@Service
override fun doRefund(transaction: TTransactionMain): AgentResponse<QrcodePayTrans> {
val agentResponse = AgentResponse<QrcodePayTrans>()
+ val originTrans = transactionService.findTransactionByRefno(transaction.reverseRefno)
+ ?: throw InternalServerError("被退款流水未找到")
val config = sourceTypeService.getConsumePaytypeConfig(TradeDict.PAYTYPE_WECHAT, transaction.shopDtl.shopaccno, false, false)
if (checkCfg(config, agentResponse)) {
+ // 生成请求参数
val bean = WechatReqResp().apply {
this.appid = config[PaytypeUtil.CFG_WECHAT_APPID]
this.mch_id = config[PaytypeUtil.CFG_WECHAT_MECHID]
this.key = config[PaytypeUtil.CFG_WECHAT_MECHKEY]
- this.out_trade_no = transaction.refno
- this.total_fee = MoneyUtil.YuanToFen(transaction.shopDtl.amount)
- this.refund_fee = MoneyUtil.YuanToFen(transaction.refundAmount)
- this.out_refund_no = transaction.reverseRefno
+ this.out_trade_no = transaction.reverseRefno
+ this.total_fee = abs(MoneyUtil.YuanToFen(originTrans.personDtl.amount))
+ this.refund_fee = abs(MoneyUtil.YuanToFen(transaction.personDtl.amount))
+ this.out_refund_no = transaction.refno
this.notify_url = StringUtil.urlAppend(config[PaytypeUtil.CFG_WECHAT_NOTIFY], transaction.tenantid)
}
bean.generaReverseSign()
val xml = bean.generaReverseXML()
- val res = restTemplate.postForEntity(PaytypeUtil.CFG_WECHAT_REFUND, xml, String::class.java)
- val eleMap = XmlUtils.parseXml(res.body)
- val retcode = eleMap["return_code"]
- val resultCode = eleMap["result_code"]
- if (!retcode.isNullOrEmpty() && "SUCCESS" == retcode
- && !resultCode.isNullOrEmpty() && "SUCCESS" == resultCode) {
- agentResponse.code = AgentCode.REQUIRE_QUERY
- agentResponse.agentCode = resultCode
- agentResponse.agentRefno = eleMap["refund_id"]
- agentResponse.agentMsg = eleMap["return_msg"]
- } else {
- logger.error { "code=${eleMap["err_code"]},des=${eleMap["err_code_des"]}" }
- agentResponse.code = AgentCode.FAIL
- agentResponse.agentCode = eleMap["err_code"]
- agentResponse.agentMsg = eleMap["err_code_des"]
+ // 设置证书
+ val password = config[PaytypeUtil.CFG_WECHAT_MECHID]!!.toCharArray()
+ val certStream = FileInputStream(File(config[PaytypeUtil.CFG_WECHAT_REFUNDCERT]!!))
+ val keyStore = KeyStore.getInstance("PKCS12")
+ certStream.use {
+ keyStore.load(it, password)
}
+ val sslContext = SSLContexts.custom()
+ //这里也是写密码的
+ .loadKeyMaterial(keyStore, password)
+ .build()
+ val socketFactory = SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.getDefaultHostnameVerifier())
+ val httpClient = HttpClients.custom()
+ .setSSLSocketFactory(socketFactory)
+ .build()
+
+ httpClient.use {
+ // 发送请求
+ val httpPost = HttpPost(PaytypeUtil.CFG_WECHAT_REFUND)
+ httpPost.entity = StringEntity(xml, "UTF-8")
+ val response = httpClient.execute(httpPost)
+ // 解析响应
+ response.use {
+ val entity = EntityUtils.toString(response.entity, "UTF-8")
+ val eleMap = XmlUtils.parseXml(entity)
+
+ val retcode = eleMap["return_code"]
+ val resultCode = eleMap["result_code"]
+ if (!retcode.isNullOrEmpty() && "SUCCESS" == retcode) {
+ if (!resultCode.isNullOrEmpty() && "SUCCESS" == resultCode) {
+ agentResponse.code = AgentCode.REQUIRE_QUERY
+ agentResponse.agentCode = resultCode
+ agentResponse.agentRefno = eleMap["refund_id"]
+ agentResponse.agentMsg = eleMap["return_msg"]
+ } else {
+ logger.error { "code=${eleMap["err_code"]},des=${eleMap["err_code_des"]}" }
+ agentResponse.code = AgentCode.FAIL
+ agentResponse.agentCode = eleMap["err_code"]
+ agentResponse.agentMsg = eleMap["err_code_des"]
+ }
+ } else {
+ logger.error { "微信退款请求通信失败:<${eleMap["return_msg"]}>" }
+ agentResponse.code = AgentCode.FAIL
+ agentResponse.agentCode = eleMap["err_code"]
+ agentResponse.agentMsg = eleMap["return_msg"]
+ }
+
+ }
+ }
+
}
return agentResponse
}
setOutTransInfo(mainDtl.outId, param.requestbillno)
}
val refundTrans = builder.refundInit(mainDtl.refno,
- param.refundAmount / 100.0, transactionService)
+ param.refundAmount / 100.0, transactionService,null,null)
transactionService.wip(refundTrans.refno)
val service = createAgentService<Any>(mainDtl.sourceType)
val resp = service.refund(refundTrans)
import com.supwisdom.dlpay.agent.domain.QrcodePayTrans
import com.supwisdom.dlpay.agent.service.AgentServiceProxy
import com.supwisdom.dlpay.api.ConsumeApi
+import com.supwisdom.dlpay.api.domain.TTransactionMain
import com.supwisdom.dlpay.api.service.ConsumePayService
import com.supwisdom.dlpay.api.service.SourceTypeService
import com.supwisdom.dlpay.api.service.TransactionServiceProxy
import com.supwisdom.dlpay.api.service.UserService
import com.supwisdom.dlpay.exception.BadRequestError
import com.supwisdom.dlpay.exception.ConflictError
+import com.supwisdom.dlpay.exception.InternalServerError
import com.supwisdom.dlpay.exception.TransactionCheckException
import com.supwisdom.dlpay.framework.ResponseBodyBuilder
import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.MoneyUtil
import com.supwisdom.dlpay.framework.util.StringUtil
import com.supwisdom.dlpay.framework.util.TradeDict
import com.supwisdom.dlpay.framework.util.TradeErrorCode
import com.supwisdom.dlpay.payapi.model.*
+import mu.KotlinLogging
import org.apache.commons.lang3.StringUtils
import org.springframework.dao.PessimisticLockingFailureException
import org.springframework.http.ResponseEntity
import org.springframework.web.context.request.NativeWebRequest
import java.util.*
import javax.validation.Valid
+import kotlin.math.abs
@RestController
@RequestMapping("\${openapi.aPITitle.base-path:/api}")
private val consumePayService: ConsumePayService,
private val transactionService: TransactionServiceProxy) : ConsumeApi {
+ private val logger = KotlinLogging.logger { }
+
override fun getRequest(): Optional<NativeWebRequest> {
return Optional.ofNullable(request)
}
// 3. 查询用户身份
val service = createAgentService<QrcodePayTrans>(qrcode.sourceType)
val agentResp = service.auth(param.shopaccno, param.billno)
+
// 4. 重新读取 qrcode 交易明细表,以获取 service.auth 查询后的结果数据
// 修改, 通过返回值来判断
// val qrcodeTransResp = agentServiceProxy.qrcodePayTransFindByMerchIdAndBillno(qrcodeTrans.agentMerchId,
if (person == null) {
qrcodeTransResp.isAnonymous = true
} else {
+ qrcodeTransResp.isAnonymous = false
apiResp.apply {
- qrcodeTransResp.isAnonymous = false
- this.userid = qrcodeTransResp.agentUserId
+ this.userid = qrcodeTransResp.userid
this.username = person.name
}
}
}
+ apiResp.apply {
+ this.anonymous = qrcodeTransResp.isAnonymous
+ }
agentServiceProxy.qrcodePayTransSaveOrUpdate(qrcodeTrans.also {
it.isAnonymous = qrcodeTransResp.isAnonymous
})
is PessimisticLockingFailureException -> throw ConflictError(e.message)
else -> {
e.printStackTrace()
- println("二维码消费初始化异常:<${e.message}>")
- throw InternalError(e.message)
+ logger.error { "二维码消费初始化异常:<${e.message}>" }
+ throw InternalServerError(e.message)
}
}
}
this.refno = transaction.refno
this.hostDate = systime.hostdate
this.hostTime = systime.hosttime
+ this.amount = abs(MoneyUtil.YuanToFen(transaction.personDtl.amount))
}
if (TradeDict.DTL_STATUS_INIT != transaction.status) {
- return when (transaction.status) {
- TradeDict.DTL_STATUS_WIP -> {
- ResponseBodyBuilder.ok(apiResponse.apply {
- this.result = TransResult.REQUIRE_QUERY
- })
- }
- TradeDict.DTL_STATUS_FAIL -> {
- ResponseBodyBuilder.ok(apiResponse.apply {
- this.result = TransResult.FAILED
- })
- }
- TradeDict.DTL_STATUS_SUCCESS -> {
- ResponseBodyBuilder.ok(apiResponse.apply {
- this.result = TransResult.ALREADY_SUCCESS
- })
- }
- else -> {
- ResponseBodyBuilder.ok(apiResponse.apply {
- this.result = TransResult.FAILED
- })
- }
- }
+ return ResponseBodyBuilder.ok(apiResponse.apply {
+ this.result = checkAlreadyTransStatus(transaction.status)
+ })
}
transactionService.qrcodeWip(param.billno, transaction)
ResponseBodyBuilder.ok(apiResponse.apply { this.result = TransResult.REQUIRE_QUERY })
}
else -> {
- ResponseBodyBuilder.ok(apiResponse.apply { this.result = TransResult.FAILED })
+ ResponseBodyBuilder.ok(apiResponse.apply {
+ this.description = response.agentMsg
+ this.result = TransResult.FAILED
+ })
}
}
} catch (e: Exception) {
is PessimisticLockingFailureException -> throw ConflictError(e.message)
else -> {
e.printStackTrace()
- println("二维码消费确认异常:<${e.message}>")
- throw InternalError(e.message)
+ logger.error { "二维码消费确认异常:<${e.message}>" }
+ throw InternalServerError(e.message)
}
}
}
override fun qrcodePayQuery(xTenantId: String, refno: String): ResponseEntity<QrcodePayConfirmResponse> {
try {
val systime = systemUtilService.sysdatetime
+
consumePayService.getTransactionMainDtl(refno, null, null)?.let {
return ResponseBodyBuilder.ok(QrcodePayConfirmResponse().apply {
this.refno = it.refno
this.hostDate = systime.hostdate
this.hostTime = systime.hosttime
+ this.amount = MoneyUtil.YuanToFen(it.personDtl.amount)
this.result = when (it.status) {
TradeDict.DTL_STATUS_SUCCESS -> TransResult.SUCCESS
TradeDict.DTL_STATUS_WIP -> TransResult.REQUIRE_QUERY
is PessimisticLockingFailureException -> throw ConflictError(e.message)
else -> {
e.printStackTrace()
- println("二维码消费查询异常:<${e.message}>")
- throw InternalError(e.message)
+ logger.error { "二维码消费查询异常:<${e.message}>" }
+ throw InternalServerError(e.message)
+ }
+ }
+ }
+ }
+
+ override fun refund(xTenantId: String, param: RefundRequest): ResponseEntity<RefundResponse> {
+ try {
+ // 查询是否重试
+ val alreadyTrans = consumePayService.queryRefundRetry(param.billno, param.shopaccno)
+ val systime = systemUtilService.sysdatetime
+ val apiResponse = RefundResponse().apply {
+ this.billno = param.billno
+ this.hostDate = systime.hostdate
+ this.hostTime = systime.hosttime
+ }
+
+ val refundTrans:TTransactionMain
+ if (alreadyTrans != null) {
+ val alreadyStatus = checkAlreadyTransStatus(alreadyTrans.status)
+ // 判断已有的流水是否是init状态
+ if (alreadyStatus != null) {
+ return ResponseBodyBuilder.ok(apiResponse.apply {
+ this.refno = alreadyTrans.refno
+ this.result = alreadyStatus
+ })
+ } else {
+ // 直接发起第三方重试
+ refundTrans = alreadyTrans
}
+ } else {
+ // 先进行流水初始化
+ refundTrans = consumePayService.initRefundTransactionMain(param)
+ }
+
+ val service = createAgentService<Any>(refundTrans.sourceType)
+ val resp = service.refund(refundTrans)
+
+ apiResponse.apply {
+ this.refno = refundTrans.refno
+ }
+ when (resp.code) {
+ AgentCode.SUCCESS -> {
+ transactionService.success(refundTrans.refno, resp.agentRefno, false)
+ return ResponseEntity.ok(apiResponse.apply { this.result = TransResult.SUCCESS })
+ }
+ AgentCode.REQUIRE_QUERY -> {
+ //待查询
+ //agentQueryResultTask.queryResult(TenantContextHolder.getContext().tenant, refundTrans)
+ transactionService.wip(refundTrans.refno)
+ return ResponseEntity.ok(apiResponse.apply { this.result = TransResult.REQUIRE_QUERY })
+ }
+ else -> transactionService.fail(refundTrans.refno,
+ "${resp.agentCode}-${resp.agentMsg}").let {
+ logger.error { "退款业务失败:<${resp.agentMsg}>" }
+ return ResponseEntity.ok(apiResponse.apply { this.result = TransResult.FAILED })
+ }
+ }
+ } catch (e: Exception) {
+ when (e) {
+ is BadRequestError -> throw BadRequestError(e.message)
+ is PessimisticLockingFailureException -> throw ConflictError(e.message)
+ else -> {
+ e.printStackTrace()
+ logger.error { "退款交易业务异常:<${e.message}>" }
+ throw InternalServerError(e.message)
+ }
+ }
+ }
+ }
+
+// override fun refundQuery(xTenantId: String, billno: String): ResponseEntity<RefundResponse> {
+//
+// }
+
+ fun checkAlreadyTransStatus(status: String): TransResult? {
+ return when (status) {
+ TradeDict.DTL_STATUS_WIP -> {
+ TransResult.REQUIRE_QUERY
+ }
+ TradeDict.DTL_STATUS_FAIL -> {
+ TransResult.FAILED
+ }
+ TradeDict.DTL_STATUS_SUCCESS -> {
+ TransResult.ALREADY_SUCCESS
+ }
+ TradeDict.DTL_STATUS_INIT -> {
+ null
+ }
+ else -> {
+ TransResult.FAILED
}
}
}
this.setOutTransInfo(request.operid, request.operSeqno)
}
val transaction = builder.refundInit(request.originRefno, request.totalAmount / 100.0,
- transactionService)
+ transactionService,null,null)
result.apply {
refno = transaction.refno
import com.supwisdom.dlpay.api.domain.TTransactionMain
import com.supwisdom.dlpay.framework.domain.TDictionary
import com.supwisdom.dlpay.payapi.model.QrcodePayConfirmRequest
+import com.supwisdom.dlpay.payapi.model.RefundRequest
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
fun getTransactionMainByParam(param: QrcodePayConfirmRequest): TTransactionMain
+
+ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+ fun initRefundTransactionMain(param: RefundRequest): TTransactionMain
+
+ @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+ fun queryRefundRetry(billno: String,shopaccno:String):TTransactionMain?
}
\ No newline at end of file
import com.supwisdom.dlpay.api.service.SourceTypeService
import com.supwisdom.dlpay.api.service.TransactionServiceProxy
import com.supwisdom.dlpay.exception.BadRequestError
+import com.supwisdom.dlpay.exception.InternalServerError
import com.supwisdom.dlpay.exception.TransactionCheckException
import com.supwisdom.dlpay.framework.dao.DictionaryDao
import com.supwisdom.dlpay.framework.domain.TDictionary
import com.supwisdom.dlpay.framework.service.SystemUtilService
import com.supwisdom.dlpay.framework.util.*
import com.supwisdom.dlpay.payapi.model.QrcodePayConfirmRequest
+import com.supwisdom.dlpay.payapi.model.RefundRequest
import com.supwisdom.multitenant.TenantContextHolder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
@Autowired
lateinit var agentServiceProxy: AgentServiceProxy
@Autowired
- lateinit var accountUtilServcie:AccountUtilServcie
+ lateinit var accountUtilServcie: AccountUtilServcie
@Autowired
- lateinit var transactionService:TransactionServiceProxy
+ lateinit var transactionService: TransactionServiceProxy
@Autowired
lateinit var agentPayServiceContext: AgentPayServiceContext
@Autowired
}
}
- override fun qrcodePayConfirmPostProcess(response: AgentResponse<QrcodePayTrans>, billno: String,transaction:TTransactionMain) {
- val qrcodeTrans = qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate,
+ override fun qrcodePayConfirmPostProcess(response: AgentResponse<QrcodePayTrans>, billno: String, transaction: TTransactionMain) {
+ qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate,
billno, TenantContextHolder.getContext().tenant.id)
?: throw BadRequestError("未找到billno")
when (response.code) {
AgentCode.SUCCESS -> {
- transactionService.qrcodeSuccess(transaction.refno, response.agentRefno,transaction)
+ transactionService.qrcodeSuccess(transaction.refno, response.agentRefno, transaction)
}
AgentCode.REQUIRE_QUERY -> {
return
}
else -> {
- transactionService.qrcodeFail(transaction.refno, response.agentMsg,transaction)
+ transactionService.qrcodeFail(transaction.refno, response.agentMsg, transaction)
}
}
}
if (!qrcodeTrans.refno.isNullOrEmpty()) {
return getTransactionMainDtl(qrcodeTrans.refno, null, null)
- ?: throw InternalError("未找到refno")
+ ?: throw InternalServerError("未找到refno")
}
//2. 初始化交易流水
return transaction
}
+
+ override fun initRefundTransactionMain(param: RefundRequest): TTransactionMain {
+ transactionMainDao.findByRefnoForUpdate(param.refno)?.let { mainDtl ->
+ if (mainDtl.sourceType.isNotEmpty()) {
+ //判断能否冲正
+ if (mainDtl.shop) {
+ checkCanReverse(mainDtl.sourceType, mainDtl.shopDtl.shopaccno)
+ } else {
+ checkCanReverse(mainDtl.sourceType)
+ }
+ } else {
+ throw BadRequestError("该笔交易未定义sourcetype, 不支持退款")
+ }
+
+ val builder = TransactionBuilder().apply {
+ setTransInfo(param.transDate, param.transTime, mainDtl.transCode, mainDtl.sourceType)
+ setOutTransInfo(mainDtl.outId, param.billno)
+ }
+ return builder.refundInit(mainDtl.refno,
+ param.amount / 100.0, transactionService, param.billno,param.shopaccno)
+ } ?: throw BadRequestError("未找到refno")
+ }
+
+ override fun queryRefundRetry(billno: String,shopaccno:String): TTransactionMain? {
+ return transactionMainDao.findByBillno(billno, shopaccno)
+ }
}
\ No newline at end of file
import com.supwisdom.dlpay.agent.dao.QrcodePayTransDao
import com.supwisdom.dlpay.api.TransactionBuilder
+import com.supwisdom.dlpay.api.dao.RefundDtlDao
import com.supwisdom.dlpay.api.dao.TransactionMainDao
import com.supwisdom.dlpay.api.domain.*
import com.supwisdom.dlpay.api.repositories.AccountService
import com.supwisdom.dlpay.api.service.AccountUtilServcie
import com.supwisdom.dlpay.api.service.SourceTypeService
import com.supwisdom.dlpay.api.service.TransactionService
+import com.supwisdom.dlpay.exception.BadRequestError
+import com.supwisdom.dlpay.exception.InternalServerError
import com.supwisdom.dlpay.exception.TransactionCheckException
import com.supwisdom.dlpay.exception.TransactionProcessException
import com.supwisdom.dlpay.framework.service.SystemUtilService
-import com.supwisdom.dlpay.framework.util.*
+import com.supwisdom.dlpay.framework.util.DateUtil
+import com.supwisdom.dlpay.framework.util.Subject
+import com.supwisdom.dlpay.framework.util.TradeDict
+import com.supwisdom.dlpay.framework.util.TradeErrorCode
import com.supwisdom.multitenant.TenantContextHolder
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
+import java.math.BigDecimal
import java.sql.SQLException
+import kotlin.math.abs
import kotlin.math.absoluteValue
@Autowired
private lateinit var qrcodePayTransDao: QrcodePayTransDao
+ @Autowired
+ private lateinit var refundDtlDao: RefundDtlDao
+
/// 公共函数部分
private fun preCheck(builder: TransactionBuilder) {
return transaction
}
- override fun qrcodeWip(billno: String,transaction:TTransactionMain): TTransactionMain {
- val qrcodeTrans = qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate,
+ override fun qrcodeWip(billno: String, transaction: TTransactionMain): TTransactionMain {
+ qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate,
billno, TenantContextHolder.getContext().tenant.id)
- ?: throw InternalError("未找到billno")
+ ?: throw InternalServerError("未找到billno")
if (transaction.status != TradeDict.DTL_STATUS_INIT) {
- throw InternalError("流水<${transaction.refno}>状态错误")
+ throw InternalServerError("流水<${transaction.refno}>状态错误")
}
updateRecordStatus(transaction, TradeDict.DTL_STATUS_WIP)
transactionMainDao.save(transaction)
transactionOnSuccess(transaction, sourcetypeRefno, false)
if (transaction.reverseType != TradeDict.REVERSE_FLAG_NONE) {
- val originTrans = transactionMainDao.findByRefnoNoLock(transaction.reverseRefno ?: "")
+ val originTrans = transactionMainDao.findByRefnoForUpdate(transaction.reverseRefno ?: "")
?: throw TransactionProcessException(TradeErrorCode.BUSINESS_DEAL_ERROR,
- "系统异常,无法处理退款流水")
+ "系统异常,被退款流水未找到")
originTrans.reverseFlag = transaction.reverseType
if (originTrans.person) {
originTrans.personDtl.reverseFlag = originTrans.reverseFlag
//////////////////////////////////////////////////////////////////
// 回退业务接口,包括撤销和退款
- override fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder): TTransactionMain {
+ override fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder, billno: String?, shopaccno: String?): TTransactionMain {
val originTrans = transactionMainDao.findByRefnoForUpdate(originRefno)
?: throw TransactionProcessException(TradeErrorCode.TRANSACTION_NOT_EXISTS
, "被退款流水不存在")
+ if (!originTrans.person) {
+ throw BadRequestError("无法退款非个人流水")
+ }
if (TradeDict.REVERSE_FLAG_NONE != originTrans.reverseType) {
throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR,
"原始流水为撤销或退款流水,不能再被退款")
throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR,
"原流水不是成功状态")
}
- if (originTrans.reverseFlag != TradeDict.REVERSE_FLAG_NONE) {
- throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR,
- "原流水已被退款或正在处理退款中")
- }
- doReversePrepareAndCheck(originTrans, builder, refundAmount)
- //fixme: 判断退款金额 check 不支持部分退款
- if (originTrans.person) {
- if (!MoneyUtil.moneyEqual(Math.abs(originTrans.personDtl.amount), refundAmount)) {
- throw TransactionProcessException(TradeErrorCode.BUSINESS_DEAL_ERROR
- , "暂不支持部分退款")
+ val tenantid = TenantContextHolder.getContext().tenant.id
+ val availableAmount: Double
+ val seqno: Int
+ // 校验原流水是否已被退款或撤销
+ when (originTrans.reverseFlag) {
+ TradeDict.REVERSE_FLAG_CANCEL -> {
+ throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR,
+ "原流水已被撤销")
+ }
+ TradeDict.REVERSE_FLAG_REFUND -> {
+ // 查询原流水最后一笔退款流水
+ val lastRefundDtl = refundDtlDao.findFirstByOriginrefnoAndTenantidOrderBySeqnoDesc(originRefno, tenantid)
+ ?: throw InternalServerError("原流水refno:<${originRefno}>的最后一笔退款流水未找到")
+ val lastTransMain = transactionMainDao.findByRefno(lastRefundDtl.refno)
+ ?: throw InternalServerError("原流水refno:<${originRefno}>的最后一笔退款主流水未找到")
+
+ when (lastTransMain.status) {
+ TradeDict.DTL_STATUS_WIP -> {
+ throw BadRequestError("原流水refno:<${originRefno}>有一笔正在处理中的退款请求")
+ }
+ TradeDict.DTL_STATUS_INIT -> {
+ lastTransMain.status = TradeDict.DTL_STATUS_CANCEL
+ transactionMainDao.save(lastTransMain)
+ throw InternalServerError("原流水refno:<${originRefno}>最后一笔退款流水已取消")
+ }
+ TradeDict.DTL_STATUS_SUCCESS -> {
+ availableAmount = lastRefundDtl.afteramount
+ }
+ TradeDict.DTL_STATUS_FAIL -> {
+ availableAmount = (BigDecimal(lastRefundDtl.afteramount)
+ + BigDecimal(lastRefundDtl.amount)).toDouble()
+ }
+ else -> {
+ throw InternalServerError("原流水refno:<${originRefno}>最后一笔退款流水状态异常")
+ }
+ }
+ seqno = lastRefundDtl.seqno + 1
+ }
+ TradeDict.REVERSE_FLAG_NONE -> {
+ availableAmount = abs(originTrans.personDtl.amount)
+ seqno = 1
}
+ else -> {
+ throw TransactionProcessException(TradeErrorCode.TRANSDTL_STATUS_ERROR,
+ "原流水的退款或撤销状态异常")
+ }
+ }
+
+ if (refundAmount > availableAmount) {
+ throw BadRequestError("退款金额不能超过原流水剩余金额")
}
- val transaction = doReverseProcess(originTrans, builder)
- originTrans.reverseFlag = TradeDict.REVERSE_FLAG_WIP
- originTrans.refundAmount = refundAmount
+ doReversePrepareAndCheck(originTrans, builder, refundAmount)
+
+ // 交易主表生成一笔退款流水
+ val transaction = doReverseProcess(originTrans, builder, refundAmount)
+ // 标记原流水为已有退款
+ originTrans.reverseFlag = TradeDict.REVERSE_FLAG_REFUND
transactionMainDao.save(originTrans)
+ // 记录退款流水表
+ val refundDtl = TRefundDtl().apply {
+ this.refno = transaction.refno
+ this.originrefno = originRefno
+ this.amount = refundAmount
+ this.afteramount = (BigDecimal(availableAmount) - BigDecimal(refundAmount)).toDouble()
+ this.seqno = seqno
+ this.outid = shopaccno
+ this.outtradeno = billno
+ this.tenantid = tenantid
+ this.tradeflag = transaction.personDtl.tradeflag
+ }
+ refundDtlDao.save(refundDtl)
+
return transaction
}
}
doReversePrepareAndCheck(originTrans, builder, 0.0)
- val transaction = doReverseProcess(originTrans, builder)
+ val transaction = doReverseProcess(originTrans, builder, 0.0)
- originTrans.reverseFlag = TradeDict.REVERSE_FLAG_WIP
+// originTrans.reverseFlag = TradeDict.REVERSE_FLAG_WIP
transactionMainDao.save(originTrans)
return transaction
}
}
}
- private fun doReverseProcess(originTrans: TTransactionMain, builder: TransactionBuilder): TTransactionMain {
+ private fun doReverseProcess(originTrans: TTransactionMain, builder: TransactionBuilder, amount: Double): TTransactionMain {
val summarySuffix = getReverseSuffix(builder.reverseType)
if (originTrans.person) {
builder.person().apply {
- setAmount(-originTrans.personDtl.amount, getOppositeTradeFlag(originTrans.personDtl.tradeflag))
+ setAmount(-amount, getOppositeTradeFlag(originTrans.personDtl.tradeflag))
setOpposite(originTrans.personDtl.oppositeAccNo, originTrans.personDtl.oppositeAccName)
}
builder.description = "${originTrans.personDtl.transdesc}$summarySuffix"
}
if (originTrans.shop) {
builder.shop().apply {
- setAmount(-originTrans.shopDtl.amount, getOppositeTradeFlag(originTrans.shopDtl.tradeflag))
+ setAmount(-amount, getOppositeTradeFlag(originTrans.shopDtl.tradeflag))
setOpposite(originTrans.shopDtl.oppositeAccNo, originTrans.shopDtl.oppositeAccName)
}
builder.description = "${originTrans.shopDtl.transdesc}$summarySuffix"
originTrans.details.forEach {
builder.addDebitCreditRecord(it.draccno, it.drsubjno, it.craccno, it.crsubjno,
- -it.amount, "${it.summary}$summarySuffix")
+ -amount, "${it.summary}$summarySuffix")
}
builder.dtltype = originTrans.dtltype
if (builder.operId.isNullOrEmpty()) {
if (originTrans.person) {
val account = accountUtilService.readAccount(originTrans.personDtl.userid)
builder.person(account).apply {
- setAmount(-originTrans.personDtl.amount,
+ setAmount(-amount,
getOppositeTradeFlag(originTrans.personDtl.tradeflag))
}
}
if (originTrans.shop) {
val shopacc = accountUtilService.readShopbyShopaccno(originTrans.shopDtl.shopaccno)
builder.shop(shopacc).apply {
- setAmount(-originTrans.shopDtl.amount,
+ setAmount(-amount,
getOppositeTradeFlag(originTrans.shopDtl.tradeflag))
}
}
* 退款类业务
*/
@Transactional
- fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder): TTransactionMain
+ fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder,billno: String?,shopaccno:String?): TTransactionMain
/**
* 撤销业务
return trans
}
- fun refund(originRefno: String, refundAmount: Double, builder: TransactionBuilder, confirm: Boolean): TTransactionMain {
- val trans = transactionService.refundInit(originRefno, refundAmount, builder)
+ fun refund(originRefno: String, refundAmount: Double,billno:String?,shopaccno:String?, builder: TransactionBuilder, confirm: Boolean): TTransactionMain {
+ val trans = transactionService.refundInit(originRefno, refundAmount, builder,billno,shopaccno)
if (confirm) {
return success(trans.refno)
}
return transactionMainDao.findByRefno(refno)
}
- fun findTransactionByRefnoForUpdate(refno: String): TTransactionMain {
- return transactionMainDao.findByRefnoForUpdate(refno)
- }
-
fun findByOutTradeNo(refno: String): TTransactionMain? {
return transactionMainDao.findByOutTradeNo(refno)
}
fun refund(originRefno: String, amount: Double, transactionService: TransactionServiceProxy): TTransactionMain {
this.reverseType = TradeDict.REVERSE_FLAG_REFUND
this.reverseRefno = originRefno
- return transactionService.refund(originRefno, amount, this, true)
+ return transactionService.refund(originRefno, amount, null, null, this, true)
}
- fun refundInit(originRefno: String, amount: Double, transactionService: TransactionServiceProxy): TTransactionMain {
+ fun refundInit(originRefno: String, amount: Double, transactionService: TransactionServiceProxy, billno: String?, shopaccno: String?): TTransactionMain {
this.reverseType = TradeDict.REVERSE_FLAG_REFUND
this.reverseRefno = originRefno
- return transactionService.refund(originRefno, amount, this, false)
+ return transactionService.refund(originRefno, amount, billno, shopaccno, this, false)
}
fun cancel(originRefno: String, transactionService: TransactionServiceProxy): TTransactionMain {
@Test
fun testHMACSHA256() {
- val token = "ddCUUHAF/OOBTAZq"
+ val token = "nR64NdrF3tjkcIHb"
val secret = "dc1d26c0d43e442588092c8d45c21bce"
val jwt = HmacUtil.HMACSHA256(token, secret)
println(jwt)