From: sijun.li Date: Fri, 20 Mar 2020 09:38:41 +0000 (+0800) Subject: 修改transaction中wip方法 X-Git-Url: https://source.supwisdom.com/gerrit/gitweb?a=commitdiff_plain;h=925a8346d76dbb4192190edad67fc9512e1782ef;p=epayment%2Ffood_payapi.git 修改transaction中wip方法 --- diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/QrcodePayConfirmResponse.java b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/QrcodePayConfirmResponse.java index 16f744dc..41e47d2d 100644 --- a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/QrcodePayConfirmResponse.java +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/QrcodePayConfirmResponse.java @@ -1,12 +1,14 @@ 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.*; @@ -15,7 +17,7 @@ import javax.validation.constraints.*; */ @javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-03-24T11:32:36.835+08:00[Asia/Shanghai]") -public class QrcodePayConfirmResponse { +public class QrcodePayConfirmResponse { @JsonProperty("refno") private String refno; @@ -31,6 +33,9 @@ public class QrcodePayConfirmResponse { @JsonProperty("result") private TransResult result; + @JsonProperty("amount") + private Integer amount; + public QrcodePayConfirmResponse refno(String refno) { this.refno = refno; return this; @@ -38,11 +43,13 @@ public class QrcodePayConfirmResponse { /** * 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; } @@ -58,11 +65,13 @@ public class QrcodePayConfirmResponse { /** * 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; } @@ -78,11 +87,13 @@ public class QrcodePayConfirmResponse { /** * 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; } @@ -98,8 +109,9 @@ public class QrcodePayConfirmResponse { /** * Get description + * * @return description - */ + */ @ApiModelProperty(value = "") @@ -118,8 +130,9 @@ public class QrcodePayConfirmResponse { /** * Get result + * * @return result - */ + */ @ApiModelProperty(value = "") @Valid @@ -132,6 +145,26 @@ public class QrcodePayConfirmResponse { 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) { @@ -146,24 +179,26 @@ public class QrcodePayConfirmResponse { 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(); } diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundRequest.java b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundRequest.java index 994ebbbc..2d5c958d 100644 --- a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundRequest.java +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundRequest.java @@ -1,13 +1,12 @@ 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 @@ -30,6 +29,9 @@ public class RefundRequest { @JsonProperty("amount") private Integer amount; + @JsonProperty("shopaccno") + private String shopaccno; + public RefundRequest billno(String billno) { this.billno = billno; return this; @@ -42,7 +44,7 @@ public class RefundRequest { @ApiModelProperty(required = true, value = "") @NotNull -@Pattern(regexp="[0-9]*") @Size(min=16) +@Pattern(regexp="[0-9]*") @Size(min=16) public String getBillno() { return billno; } @@ -63,7 +65,7 @@ public class RefundRequest { @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; } @@ -84,7 +86,7 @@ public class RefundRequest { @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; } @@ -105,7 +107,7 @@ public class RefundRequest { @ApiModelProperty(required = true, value = "") @NotNull -@Pattern(regexp="[0-9]*") @Size(min=16) +@Pattern(regexp="[0-9]*") @Size(min=16) public String getRefno() { return refno; } @@ -135,6 +137,26 @@ public class RefundRequest { 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) { @@ -149,24 +171,26 @@ public class RefundRequest { 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(); } diff --git a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundResponse.java b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundResponse.java index bb7892c1..4321f4df 100644 --- a/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundResponse.java +++ b/payapi-common/src/main/java/com/supwisdom/dlpay/payapi/model/RefundResponse.java @@ -1,14 +1,13 @@ 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.*; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; +import javax.validation.constraints.Size; +import java.util.Objects; /** * RefundResponse @@ -28,6 +27,12 @@ public class RefundResponse { @JsonProperty("refno") private String refno; + @JsonProperty("refundAmount") + private Integer refundAmount; + + @JsonProperty("leftAmount") + private Integer leftAmount; + @JsonProperty("result") private TransResult result; @@ -111,6 +116,48 @@ public class RefundResponse { this.refno = refno; } + public RefundResponse refundAmount(Integer refundAmount) { + this.refundAmount = refundAmount; + return this; + } + + /** + * Get refundAmount + * @return refundAmount + */ + @ApiModelProperty(required = true, value = "") + @NotNull + + + public Integer getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(Integer refundAmount) { + this.refundAmount = refundAmount; + } + + public RefundResponse leftAmount(Integer leftAmount) { + this.leftAmount = leftAmount; + return this; + } + + /** + * Get leftAmount + * @return leftAmount + */ + @ApiModelProperty(required = true, value = "") + @NotNull + + + public Integer getLeftAmount() { + return leftAmount; + } + + public void setLeftAmount(Integer leftAmount) { + this.leftAmount = leftAmount; + } + public RefundResponse result(TransResult result) { this.result = result; return this; @@ -146,12 +193,14 @@ public class RefundResponse { Objects.equals(this.hostDate, refundResponse.hostDate) && Objects.equals(this.hostTime, refundResponse.hostTime) && Objects.equals(this.refno, refundResponse.refno) && + Objects.equals(this.refundAmount, refundResponse.refundAmount) && + Objects.equals(this.leftAmount, refundResponse.leftAmount) && Objects.equals(this.result, refundResponse.result); } @Override public int hashCode() { - return Objects.hash(billno, hostDate, hostTime, refno, result); + return Objects.hash(billno, hostDate, hostTime, refno, refundAmount, leftAmount,result); } @Override @@ -163,6 +212,8 @@ public class RefundResponse { sb.append(" hostDate: ").append(toIndentedString(hostDate)).append("\n"); sb.append(" hostTime: ").append(toIndentedString(hostTime)).append("\n"); sb.append(" refno: ").append(toIndentedString(refno)).append("\n"); + sb.append(" refundAmount: ").append(toIndentedString(refundAmount)).append("\n"); + sb.append(" leftAmount: ").append(toIndentedString(leftAmount)).append("\n"); sb.append(" result: ").append(toIndentedString(result)).append("\n"); sb.append("}"); return sb.toString(); diff --git a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java index fabcbbfc..383ab9be 100644 --- a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java +++ b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/ConsumePropxy.java @@ -1,10 +1,7 @@ package com.supwisdom.dlpay.paysdk.proxy; import com.supwisdom.dlpay.api.bean.*; -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.*; import com.supwisdom.mutlitenant.client.annotations.JwtMethod; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.*; @@ -42,4 +39,14 @@ public interface ConsumePropxy { @JwtMethod QrcodePayConfirmResponse qrcodePayQuery(@RequestHeader(name = "X-TENANT-ID") String xTenantId, @PathVariable("refno") String refno); + + @RequestMapping(value = "/refund", method = RequestMethod.POST) + @JwtMethod + RefundResponse refund(@RequestHeader(name = "X-TENANT-ID") String xTenantId, + @Valid @RequestBody RefundRequest refundRequest); + + @RequestMapping(value = "/refundQuery", method = RequestMethod.GET) + @JwtMethod + RefundResponse refundQuery(@RequestHeader(name = "X-TENANT-ID") String xTenantId, + @Valid @RequestBody RefundQueryRequest refundQueryRequest); } diff --git a/payapi-spec/consumeapi.yaml b/payapi-spec/consumeapi.yaml index b58bf96e..7114293f 100644 --- a/payapi-spec/consumeapi.yaml +++ b/payapi-spec/consumeapi.yaml @@ -86,6 +86,10 @@ components: result: title: 交易结果 $ref: 'definitions.yaml#/components/schemas/TransResult' + amount: + type: integer + format: int32 + title: 消费金额 RefundRequest: type: object title: 退款申请 @@ -95,6 +99,7 @@ components: - transTime - refno - amount + - shopaccno properties: billno: title: 退款申请订单号 @@ -106,6 +111,8 @@ components: refno: title: 退款申请原始订单交易参考号 $ref: 'definitions.yaml#/components/schemas/Refno' + shopaccno: + $ref: 'definitions.yaml#/components/schemas/ShopAccNo' amount: type: integer title: 退款金额, 正式 @@ -123,6 +130,14 @@ components: refno: title: 退款申请原始订单交易参考号 $ref: 'definitions.yaml#/components/schemas/Refno' + refundAmount: + type: integer + format: int32 + title: 退款金额 + leftAmount: + type: integer + format: int32 + title: 原流水剩余金额 result: title: 退款处理结果 $ref: 'definitions.yaml#/components/schemas/TransResult' @@ -235,13 +250,13 @@ components: refundQuery: parameters: - $ref: 'definitions.yaml#/components/headers/TenantId' - - name: billno + - name: refno in: path - description: 退款订单号 + description: 系统交易参考号 required: true schema: type: string - title: 退款订单号 + title: 系统交易参考号 get: description: 退款申请查询 tags: diff --git a/payapi-spec/payapi-spec.yaml b/payapi-spec/payapi-spec.yaml index 91197efe..6a617afc 100644 --- a/payapi-spec/payapi-spec.yaml +++ b/payapi-spec/payapi-spec.yaml @@ -13,7 +13,7 @@ paths: $ref: consumeapi.yaml#/components/paths/qrcodePayQuery /consume/refund: $ref: consumeapi.yaml#/components/paths/refund - /consume/refund/query/{billno}: + /consume/refund/query/{refno}: $ref: consumeapi.yaml#/components/paths/refundQuery /account/query: $ref: accountapi.yaml#/components/paths/accountQuery diff --git a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java index e1cbc30c..df13f9f0 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java @@ -42,6 +42,7 @@ public class YnrccUtil { public static final String DTL_STATUS_PART_REFUND = "3"; //部分退款 public static final int QUERY_MAX_COUNT = 3; //最大查询次数 + public static final int PAY_QUERY_MAX_COUNT = 5; public static final String YNRCC_BILLS_DOWNLOAD_LASTDATE = "ynrcc.download.bills.lastdate"; diff --git a/payapi/src/main/java/com/supwisdom/dlpay/agent/domain/QrcodePayTrans.java b/payapi/src/main/java/com/supwisdom/dlpay/agent/domain/QrcodePayTrans.java index 4619435d..4b1f7429 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/agent/domain/QrcodePayTrans.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/agent/domain/QrcodePayTrans.java @@ -71,6 +71,9 @@ public class QrcodePayTrans implements Serializable { @Column(name = "spip", length = 20) private String spip; + @Transient + private Double leftAmount; + public QrcodePayTrans() { } @@ -193,4 +196,12 @@ public class QrcodePayTrans implements Serializable { public void setSpip(String spip) { this.spip = spip; } + + public Double getLeftAmount() { + return leftAmount; + } + + public void setLeftAmount(Double leftAmount) { + this.leftAmount = leftAmount; + } } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/ConsumeApi.java b/payapi/src/main/java/com/supwisdom/dlpay/api/ConsumeApi.java index ce7cf2c6..850430ec 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/ConsumeApi.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/ConsumeApi.java @@ -5,34 +5,18 @@ */ 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-24T11:22:47.201+08:00[Asia/Shanghai]") +@javax.annotation.Generated(value = "org.openapitools.codegen.languages.SpringCodegen", date = "2020-03-17T08:49:02.541+08:00[Asia/Shanghai]") @Validated @Api(value = "consume", description = "the consume API") @@ -46,12 +30,12 @@ public interface ConsumeApi { @ApiResponses(value = { @ApiResponse(code = 200, message = "交易成功", response = QrcodePayConfirmResponse.class), @ApiResponse(code = 409, message = "交易正忙,稍后重试", response = ErrorResponse.class), - @ApiResponse(code = 500, message = "请求失败", response = ErrorResponse.class) }) + @ApiResponse(code = 200, message = "交易失败", response = ErrorResponse.class) }) @RequestMapping(value = "/consume/qrcode/confirm", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - default ResponseEntity qrcodePayConfirm(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,@ApiParam(value = "Qrcode确认" ) @Valid @RequestBody QrcodePayConfirmRequest qrcodePayConfirmRequest) { + default ResponseEntity qrcodePayConfirm(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-Tenant-Id", required=true) String xTenantId,@ApiParam(value = "Qrcode确认" ) @Valid @RequestBody QrcodePayConfirmRequest qrcodePayConfirmRequest) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { @@ -69,12 +53,12 @@ public interface ConsumeApi { @ApiOperation(value = "", nickname = "qrcodePayInit", notes = "", response = QrcodePayInitResponse.class, tags={ "pos", }) @ApiResponses(value = { @ApiResponse(code = 200, message = "初始化成功", response = QrcodePayInitResponse.class), - @ApiResponse(code = 500, message = "请求失败", response = ErrorResponse.class) }) + @ApiResponse(code = 200, message = "请求错误", response = ErrorResponse.class) }) @RequestMapping(value = "/consume/qrcode/init", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - default ResponseEntity qrcodePayInit(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,@ApiParam(value = "QrCode 初始化" ) @Valid @RequestBody QrcodePayInitRequest qrcodePayInitRequest) { + default ResponseEntity qrcodePayInit(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String xTenantId,@ApiParam(value = "QrCode 初始化" ) @Valid @RequestBody QrcodePayInitRequest qrcodePayInitRequest) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { @@ -92,11 +76,11 @@ public interface ConsumeApi { @ApiOperation(value = "", nickname = "qrcodePayQuery", notes = "根据系统交易参考号查询流水状态", response = QrcodePayConfirmResponse.class, tags={ "pos", }) @ApiResponses(value = { @ApiResponse(code = 200, message = "查询成功", response = QrcodePayConfirmResponse.class), - @ApiResponse(code = 500, message = "请求失败", response = ErrorResponse.class) }) + @ApiResponse(code = 200, message = "查询失败", response = ErrorResponse.class) }) @RequestMapping(value = "/consume/qrcode/query/{refno}", produces = { "application/json" }, method = RequestMethod.GET) - default ResponseEntity qrcodePayQuery(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,@ApiParam(value = "系统交易参考号",required=true) @PathVariable("refno") String refno) { + default ResponseEntity qrcodePayQuery(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String xTenantId,@ApiParam(value = "系统交易参考号",required=true) @PathVariable("refno") String refno) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { @@ -114,13 +98,13 @@ public interface ConsumeApi { @ApiOperation(value = "", nickname = "refund", notes = "退款交易", response = RefundResponse.class, tags={ "pos", }) @ApiResponses(value = { @ApiResponse(code = 200, message = "退款申请成功,等待查询", response = RefundResponse.class), - @ApiResponse(code = 409, message = "退款申请失败,订单号已存在或正在退款中 ", response = ErrorResponse.class), - @ApiResponse(code = 500, message = "请求失败", response = ErrorResponse.class) }) + @ApiResponse(code = 409, message = "退款申请失败,订单号已存在或正在退款中", response = ErrorResponse.class), + @ApiResponse(code = 200, message = "退款申请失败", response = ErrorResponse.class) }) @RequestMapping(value = "/consume/refund", produces = { "application/json" }, consumes = { "application/json" }, method = RequestMethod.POST) - default ResponseEntity refund(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,@ApiParam(value = "" ) @Valid @RequestBody RefundRequest refundRequest) { + default ResponseEntity refund(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-Tenant-Id", required=true) String xTenantId,@ApiParam(value = "" ) @Valid @RequestBody RefundRequest refundRequest) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { @@ -139,11 +123,11 @@ public interface ConsumeApi { @ApiResponses(value = { @ApiResponse(code = 200, message = "退款申请查询成功", response = RefundResponse.class), @ApiResponse(code = 404, message = "退款申请订单不存在", response = ErrorResponse.class), - @ApiResponse(code = 500, message = "请求失败", response = ErrorResponse.class) }) - @RequestMapping(value = "/consume/refund/query/{billno}", + @ApiResponse(code = 200, message = "退款申请失败", response = ErrorResponse.class) }) + @RequestMapping(value = "/consume/refund/query/{refno}", produces = { "application/json" }, method = RequestMethod.GET) - default ResponseEntity refundQuery(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-TENANT-ID", required=true) String X_TENANT_ID,@ApiParam(value = "退款订单号",required=true) @PathVariable("billno") String billno) { + default ResponseEntity refundQuery(@ApiParam(value = "租户ID" ,required=true) @RequestHeader(value="X-Tenant-Id", required=true) String xTenantId,@ApiParam(value = "系统交易参考号",required=true) @PathVariable("refno") String refno) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) { diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/bean/WechatReqResp.java b/payapi/src/main/java/com/supwisdom/dlpay/api/bean/WechatReqResp.java index 9cc15b90..ab927ba8 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/bean/WechatReqResp.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/bean/WechatReqResp.java @@ -2,6 +2,7 @@ package com.supwisdom.dlpay.api.bean; import com.supwisdom.dlpay.framework.util.MD5; import com.supwisdom.dlpay.framework.util.RandomUtils; +import com.supwisdom.dlpay.framework.util.StringUtil; /** * Created by shuwei on 17/6/12. @@ -242,9 +243,12 @@ public class WechatReqResp { StringBuffer str = new StringBuffer() .append("appid=").append(this.appid) .append("&mch_id=").append(this.mch_id) - .append("&nonce_str=").append(nonce_str) - .append("&out_trade_no=").append(this.out_trade_no); - str.append("&key=").append(this.key); + .append("&nonce_str=").append(nonce_str); + if (!StringUtil.isEmpty(this.out_refund_no)) { + str.append("&out_refund_no=").append(this.out_refund_no); + } + str.append("&out_trade_no=").append(this.out_trade_no). + append("&key=").append(this.key); this.sign = MD5.encodeByMD5(str.toString()); return sign; } @@ -260,7 +264,14 @@ public class WechatReqResp { .append("") .append("") .append(this.nonce_str) - .append("") + .append(""); + if (!StringUtil.isEmpty(this.out_refund_no)) { + requestStr + .append("") + .append(this.out_refund_no) + .append(""); + } + requestStr .append("") .append(this.out_trade_no) .append("") diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/RefundDtlDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/RefundDtlDao.java new file mode 100644 index 00000000..7f859223 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/RefundDtlDao.java @@ -0,0 +1,10 @@ +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 findFirstByOriginrefnoAndTenantidOrderBySeqnoDesc(String originrefno, String tenantid); + + TRefundDtl findByRefno(String refno); +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java index e6a87341..446c128b 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java @@ -8,6 +8,7 @@ import org.springframework.data.repository.CrudRepository; import javax.persistence.LockModeType; import javax.persistence.QueryHint; +import java.util.List; public interface TransactionMainDao extends CrudRepository { @@ -30,4 +31,6 @@ public interface TransactionMainDao extends CrudRepository findByReverseRefno(String refno); } diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TRefundDtl.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TRefundDtl.java new file mode 100644 index 00000000..80acea67 --- /dev/null +++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TRefundDtl.java @@ -0,0 +1,130 @@ +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; + } +} diff --git a/payapi/src/main/java/com/supwisdom/dlpay/util/PaytypeUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/util/PaytypeUtil.java index e32d2f85..f964872d 100644 --- a/payapi/src/main/java/com/supwisdom/dlpay/util/PaytypeUtil.java +++ b/payapi/src/main/java/com/supwisdom/dlpay/util/PaytypeUtil.java @@ -50,6 +50,8 @@ public class PaytypeUtil { public static final String CFG_WECHAT_QUERY = "https://api.mch.weixin.qq.com/pay/orderquery"; + public static final String CFG_WECHAT_REFUND_QUERY = "https://api.mch.weixin.qq.com/pay/refundquery"; + public static final String CFG_WECHAT_REFUND = "https://api.mch.weixin.qq.com/secapi/pay/refund"; } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/wechat_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/wechat_service_impl.kt index 6084118a..55babc35 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/wechat_service_impl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/impl/wechat_service_impl.kt @@ -12,15 +12,26 @@ 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.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 @@ -225,38 +236,68 @@ class WechatServiceImpl(val sourceTypeService: SourceTypeService, override fun doQuery(transaction: TTransactionMain): AgentResponse { val agentResponse = AgentResponse() val config = sourceTypeService.getConsumePaytypeConfig(TradeDict.PAYTYPE_WECHAT, transaction.shopDtl.shopaccno, false, false) + val refundFlag = TradeDict.REVERSE_FLAG_REFUND == transaction.reverseType if (checkCfg(config, agentResponse)) { //H5 支付 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 + if (!refundFlag) { + this.out_trade_no = transaction.refno + } else { + this.out_refund_no = transaction.refno + } } bean.generalQuerySign() val xml = bean.generalQueryXML() restTemplate.messageConverters[1] = StringHttpMessageConverter(StandardCharsets.UTF_8) - val res = restTemplate.postForEntity(PaytypeUtil.CFG_WECHAT_QUERY, xml, String::class.java) + val url = if (!refundFlag) PaytypeUtil.CFG_WECHAT_QUERY else PaytypeUtil.CFG_WECHAT_REFUND_QUERY + val res = restTemplate.postForEntity(url, xml, String::class.java) val eleMap = XmlUtils.parseXml(res.body) val retcode = eleMap["return_code"] val resultCode = eleMap["result_code"] - val tradeState = eleMap["trade_state"] - if (!retcode.isNullOrEmpty() && "SUCCESS" == retcode - && !resultCode.isNullOrEmpty() && "SUCCESS" == resultCode) { - agentResponse.code = AgentCode.REQUIRE_QUERY - agentResponse.agentCode = resultCode - agentResponse.agentMsg = eleMap["return_msg"] - when (tradeState) { - "SUCCESS" -> agentResponse.dtlStatus = DtlStatus.SUCCESS - "REFUND" -> agentResponse.dtlStatus = DtlStatus.REFUND - "NOTPAY", "CLOSED", "REVOKED", "PAYERROR" -> agentResponse.dtlStatus = DtlStatus.FAIL - "USERPAYING" -> agentResponse.dtlStatus = DtlStatus.WAIT + if (!retcode.isNullOrEmpty() && "SUCCESS" == retcode) { + if (!resultCode.isNullOrEmpty() && "SUCCESS" == resultCode) { + agentResponse.code = AgentCode.SUCCESS + agentResponse.agentCode = resultCode + if (!refundFlag) { + when (eleMap["trade_state"]) { + "SUCCESS" -> { + agentResponse.dtlStatus = DtlStatus.SUCCESS + agentResponse.agentRefno = eleMap["transaction_id"] + } + "REFUND" -> agentResponse.dtlStatus = DtlStatus.REFUND + "NOTPAY", "CLOSED", "REVOKED", "PAYERROR" -> agentResponse.dtlStatus = DtlStatus.FAIL + "USERPAYING" -> { + agentResponse.code = AgentCode.REQUIRE_QUERY + agentResponse.dtlStatus = DtlStatus.WAIT + } + } + } else { + when (eleMap["refund_status_0"]) { + "SUCCESS" -> { + agentResponse.dtlStatus = DtlStatus.SUCCESS + agentResponse.agentRefno = eleMap["refund_id_0"] + } + "REFUNDCLOSE", "CHANGE" -> agentResponse.dtlStatus = DtlStatus.FAIL + "PROCESSING" -> { + agentResponse.code = AgentCode.REQUIRE_QUERY + agentResponse.dtlStatus = DtlStatus.WAIT + } + } + } + } 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 { "code=${eleMap["err_code"]}},des=${eleMap["err_code_des"]}" } + logger.error { "code=${retcode}},des=${eleMap["return_msg"]}" } agentResponse.code = AgentCode.FAIL - agentResponse.agentCode = eleMap["err_code"] - agentResponse.agentMsg = eleMap["err_code_des"] + agentResponse.agentCode = retcode + agentResponse.agentMsg = eleMap["return_msg"] } } return agentResponse @@ -264,36 +305,73 @@ class WechatServiceImpl(val sourceTypeService: SourceTypeService, override fun doRefund(transaction: TTransactionMain): AgentResponse { val agentResponse = AgentResponse() + 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 } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/inapp_alipay.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/inapp_alipay.kt index 6905ad9a..2c70a2ef 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/inapp_alipay.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/agent/service/inapp_alipay.kt @@ -43,8 +43,8 @@ class AlipayInAppServcie(private val alipayService: AlipayService, } } val transation = transactionServiceProxy.findTransactionByRefno(refno) - val resp = alipayService.doPayNotify(transation,map) - if (resp.code == AgentCode.SUCCESS) { + val resp = alipayService.doPayNotify(transation, map) + if (resp.code == AgentCode.SUCCESS) { alipayInAppResponse.payload = transactionServiceProxy.success(refno) alipayInAppResponse.agentBody = "success" } else { diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt index db1aaf34..f2872823 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt @@ -113,10 +113,10 @@ class AgentQueryResultTask(private val transactionService: TransactionServicePro private val transactionContainer = TransactionContainer("qrcode.pay", redisConnectionFactory) @Recover - fun quertRecover(ex: TaskRetryException, details: TenantDetails, + fun quertRecover(ex: Exception, details: TenantDetails, transaction: TTransactionMain) { TenantContextHolder.getContext().tenant = details - logger.error("查询refno=[${transaction.refno}]流水结果超最大查询次数[${YnrccUtil.QUERY_MAX_COUNT}]") + logger.error("查询refno=[${transaction.refno}]流水结果超最大查询次数[${YnrccUtil.PAY_QUERY_MAX_COUNT}]") dtlQueryResultService.saveOrUpdateDtlQuery(TDtlQuery().apply { this.accdate = transaction.accdate this.refno = transaction.refno @@ -128,7 +128,7 @@ class AgentQueryResultTask(private val transactionService: TransactionServicePro } @Async("queryAgentPayResult") - @Retryable(value = [TaskRetryException::class], maxAttempts = YnrccUtil.QUERY_MAX_COUNT, backoff = Backoff(value = 3000)) + @Retryable(include = [TaskRetryException::class], maxAttempts = YnrccUtil.PAY_QUERY_MAX_COUNT, backoff = Backoff(value = 5000)) fun queryResult(details: TenantDetails, transaction: TTransactionMain) { TenantContextHolder.getContext().tenant = details diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt index f5003275..d8c8375a 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/consume_api_controller.kt @@ -24,7 +24,10 @@ import org.springframework.data.redis.connection.RedisConnectionFactory import org.springframework.data.redis.core.RedisTemplate import org.springframework.http.ResponseEntity import org.springframework.validation.annotation.Validated -import org.springframework.web.bind.annotation.* +import org.springframework.web.bind.annotation.PostMapping +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RestController import javax.servlet.http.HttpServletRequest import javax.validation.Valid @@ -334,7 +337,7 @@ class DaliConsumeAPIController(private val qrCodeService: QRCodeService, 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(mainDtl.sourceType) val resp = service.refund(refundTrans) @@ -449,7 +452,7 @@ class DaliConsumeAPIController(private val qrCodeService: QRCodeService, .fail(TradeErrorCode.BUSINESS_DEAL_ERROR, "交易扣费失败")) } -// @RequestMapping("/qrcode/init", method = [RequestMethod.POST, RequestMethod.GET]) + // @RequestMapping("/qrcode/init", method = [RequestMethod.POST, RequestMethod.GET]) fun qrcodePayInit(@Validated(InitAction::class) @RequestBody param: QrcodePayParam) : ResponseEntity { val apiResp = QrcodePayResponse() @@ -528,7 +531,7 @@ class DaliConsumeAPIController(private val qrCodeService: QRCodeService, private fun qrcodeSummary(st: TSourceType): String = st.paydesc + "扫码付" -// @PostMapping("/qrcode/confirm") + // @PostMapping("/qrcode/confirm") fun qrcodePayConfirm(@Validated(ConfirmAction::class) @RequestBody param: QrcodePayParam) : ResponseEntity { //1. 交易检查 @@ -622,7 +625,7 @@ class DaliConsumeAPIController(private val qrCodeService: QRCodeService, val response = service.pay(transaction) return when (response.code) { AgentCode.SUCCESS -> { - transactionService.success(transaction.refno, response.agentRefno) + transactionService.success(transaction.refno, response.agentRefno, null) ResponseEntity.ok(ResponseBodyBuilder.create() .success(apiResponse)) } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/k12_consume_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/k12_consume_api_controller.kt index 7bf36cd6..14916b48 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/k12_consume_api_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/k12_consume_api_controller.kt @@ -5,20 +5,26 @@ import com.supwisdom.dlpay.agent.AgentPayService import com.supwisdom.dlpay.agent.AgentPayServiceContext import com.supwisdom.dlpay.agent.domain.QrcodePayTrans import com.supwisdom.dlpay.agent.service.AgentServiceProxy +import com.supwisdom.dlpay.api.AgentQueryResultTask 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 com.supwisdom.multitenant.TenantContextHolder +import mu.KotlinLogging import org.apache.commons.lang3.StringUtils import org.springframework.dao.PessimisticLockingFailureException import org.springframework.http.ResponseEntity @@ -27,6 +33,7 @@ import org.springframework.web.bind.annotation.RestController 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}") @@ -38,7 +45,10 @@ constructor(private val request: NativeWebRequest, private val agentPayServiceContext: AgentPayServiceContext, private val userService: UserService, private val consumePayService: ConsumePayService, - private val transactionService: TransactionServiceProxy) : ConsumeApi { + private val transactionService: TransactionServiceProxy, + private val agentQueryResultTask: AgentQueryResultTask) : ConsumeApi { + + private val logger = KotlinLogging.logger { } override fun getRequest(): Optional { return Optional.ofNullable(request) @@ -72,6 +82,7 @@ constructor(private val request: NativeWebRequest, // 3. 查询用户身份 val service = createAgentService(qrcode.sourceType) val agentResp = service.auth(param.shopaccno, param.billno) + // 4. 重新读取 qrcode 交易明细表,以获取 service.auth 查询后的结果数据 // 修改, 通过返回值来判断 // val qrcodeTransResp = agentServiceProxy.qrcodePayTransFindByMerchIdAndBillno(qrcodeTrans.agentMerchId, @@ -93,13 +104,16 @@ constructor(private val request: NativeWebRequest, 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 }) @@ -119,8 +133,8 @@ constructor(private val request: NativeWebRequest, is PessimisticLockingFailureException -> throw ConflictError(e.message) else -> { e.printStackTrace() - println("二维码消费初始化异常:<${e.message}>") - throw InternalError(e.message) + logger.error { "二维码消费初始化异常:<${e.message}>" } + throw InternalServerError(e.message) } } } @@ -141,47 +155,39 @@ constructor(private val request: NativeWebRequest, 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) + transactionService.wip(transaction.refno) val service = createAgentService(transaction.sourceType) val response = service.pay(transaction) - consumePayService.qrcodePayConfirmPostProcess(response, param.billno, transaction) + consumePayService.qrcodePayConfirmPostProcess(response, transaction.refno) return when (response.code) { AgentCode.SUCCESS -> { - ResponseBodyBuilder.ok(apiResponse.apply { this.result = TransResult.SUCCESS }) + ResponseBodyBuilder.ok(apiResponse.apply { + this.description = response.agentMsg + this.result = TransResult.SUCCESS + }) } AgentCode.REQUIRE_QUERY -> { - ResponseBodyBuilder.ok(apiResponse.apply { this.result = TransResult.REQUIRE_QUERY }) + agentQueryResultTask.queryResult(TenantContextHolder.getContext().tenant, transaction) + ResponseBodyBuilder.ok(apiResponse.apply { + this.description = response.agentMsg + 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) { @@ -190,8 +196,8 @@ constructor(private val request: NativeWebRequest, is PessimisticLockingFailureException -> throw ConflictError(e.message) else -> { e.printStackTrace() - println("二维码消费确认异常:<${e.message}>") - throw InternalError(e.message) + logger.error { "二维码消费确认异常:<${e.message}>" } + throw InternalServerError(e.message) } } } @@ -200,15 +206,20 @@ constructor(private val request: NativeWebRequest, override fun qrcodePayQuery(xTenantId: String, refno: String): ResponseEntity { 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 = abs(MoneyUtil.YuanToFen(it.personDtl.amount)) this.result = when (it.status) { TradeDict.DTL_STATUS_SUCCESS -> TransResult.SUCCESS TradeDict.DTL_STATUS_WIP -> TransResult.REQUIRE_QUERY - TradeDict.DTL_STATUS_FAIL -> TransResult.FAILED + TradeDict.DTL_STATUS_FAIL -> { + this.description = it.personDtl.remark + TransResult.FAILED + } else -> TransResult.FAILED } }) @@ -219,9 +230,124 @@ constructor(private val request: NativeWebRequest, 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 { + 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) { + if (alreadyTrans.reverseType != TradeDict.REVERSE_FLAG_REFUND) { + throw BadRequestError("退款请更换billno,该billno已使用") + } + 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(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 -> { + //待查询 + transactionService.wip(refundTrans.refno) + agentQueryResultTask.queryResult(TenantContextHolder.getContext().tenant, refundTrans) + 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, refno: String): ResponseEntity { + val systime = systemUtilService.sysdatetime + consumePayService.getTransactionMainDtl(refno, null, null)?.let { + if (it.reverseType != TradeDict.REVERSE_FLAG_REFUND) { + throw BadRequestError("该笔交易不是退款交易") + } + val refundDtl = consumePayService.queryRefundDtl(refno) + ?: throw InternalServerError("未找到refno:<${refno}") + return ResponseBodyBuilder.ok(RefundResponse().apply { + this.billno = it.outTradeNo + this.refno = it.refno + this.hostDate = systime.hostdate + this.hostTime = systime.hosttime + this.leftAmount= abs(MoneyUtil.YuanToFen(refundDtl.afteramount)) + this.refundAmount = abs(MoneyUtil.YuanToFen(it.personDtl.amount)) + this.result = when (it.status) { + TradeDict.DTL_STATUS_SUCCESS -> TransResult.SUCCESS + TradeDict.DTL_STATUS_WIP -> TransResult.REQUIRE_QUERY + TradeDict.DTL_STATUS_INIT -> TransResult.REQUIRE_QUERY + TradeDict.DTL_STATUS_FAIL -> TransResult.FAILED + else -> TransResult.FAILED + } + }) + } ?: throw BadRequestError("未找到refno:<${refno}") + + } + + 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 } } } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt index 09697b57..ac08430c 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/transaction_controller.kt @@ -93,9 +93,9 @@ class TransactionController(private val transactionServiceProxy: TransactionServ @PostMapping("/inapp/payinit") fun inAppPayInit(@Valid @RequestBody param: InAppPayParam): ResponseEntity<*> { val response = InAppPayResponse() - /* if (transactionContainer.count() > 100) { - return ResponseBodyBuilder.internalServerError("第三方请求异常") - }*/ + /* if (transactionContainer.count() > 100) { + return ResponseBodyBuilder.internalServerError("第三方请求异常") + }*/ val sourceType = sourceTypeService.getBySourceType(param.sourceType) ?: return ResponseBodyBuilder.badRequest("source type <${param.sourceType}> 不存在") @@ -109,7 +109,7 @@ class TransactionController(private val transactionServiceProxy: TransactionServ "native" -> TradeCode.TRANSCODE_APPPAY else -> return ResponseBodyBuilder.badRequest("app typ <${param.inAppType}> 不支持") } - if(param.description==null){ + if (param.description == null) { param.description = "描述为空" } val builder = TransactionBuilder() diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt index ae4c7a72..8475a22c 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/user_api_controller.kt @@ -323,7 +323,7 @@ class UserAPIController { 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 diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/consume_pay_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/consume_pay_service.kt index 832cf5ce..2dc45e33 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/consume_pay_service.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/consume_pay_service.kt @@ -2,9 +2,11 @@ package com.supwisdom.dlpay.api.service import com.supwisdom.dlpay.agent.AgentResponse import com.supwisdom.dlpay.agent.domain.QrcodePayTrans +import com.supwisdom.dlpay.api.domain.TRefundDtl 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 @@ -28,8 +30,17 @@ interface ConsumePayService { fun checkCanReverse(sourcetype: String, shopaccno: String? = null): Boolean @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class]) - fun qrcodePayConfirmPostProcess(response: AgentResponse, billno: String,transaction:TTransactionMain) + fun qrcodePayConfirmPostProcess(response: AgentResponse, refno: String) @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? + + @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class]) + fun queryRefundDtl(refno:String):TRefundDtl? } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/consume_pay_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/consume_pay_service_impl.kt index 9d8b5c44..38a1beee 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/consume_pay_service_impl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/consume_pay_service_impl.kt @@ -9,7 +9,9 @@ import com.supwisdom.dlpay.agent.domain.QrcodePayTrans import com.supwisdom.dlpay.agent.service.AgentServiceProxy import com.supwisdom.dlpay.api.AccountProxy 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.TRefundDtl import com.supwisdom.dlpay.api.domain.TSourceType import com.supwisdom.dlpay.api.domain.TTransactionMain import com.supwisdom.dlpay.api.exception.RequestParamCheckException @@ -18,12 +20,14 @@ 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.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 @@ -39,15 +43,17 @@ class ConsumePayServiceImpl : ConsumePayService { @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 lateinit var systemUtilService: SystemUtilService @Autowired lateinit var qrcodePayTransDao: QrcodePayTransDao + @Autowired + lateinit var refundDtlDao: RefundDtlDao override fun checkShopPaytype(shopaccno: String, sourceType: String, anonymousflag: Boolean?): Boolean { return sourceTypeService.checkShopSourceType(shopaccno, sourceType, true == anonymousflag) @@ -82,19 +88,16 @@ class ConsumePayServiceImpl : ConsumePayService { } } - override fun qrcodePayConfirmPostProcess(response: AgentResponse, billno: String,transaction:TTransactionMain) { - val qrcodeTrans = qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate, - billno, TenantContextHolder.getContext().tenant.id) - ?: throw BadRequestError("未找到billno") + override fun qrcodePayConfirmPostProcess(response: AgentResponse, refno: String) { when (response.code) { AgentCode.SUCCESS -> { - transactionService.qrcodeSuccess(transaction.refno, response.agentRefno,transaction) + transactionService.success(refno, response.agentRefno, null) } AgentCode.REQUIRE_QUERY -> { return } else -> { - transactionService.qrcodeFail(transaction.refno, response.agentMsg,transaction) + transactionService.fail(refno, response.agentMsg) } } } @@ -130,7 +133,7 @@ class ConsumePayServiceImpl : ConsumePayService { if (!qrcodeTrans.refno.isNullOrEmpty()) { return getTransactionMainDtl(qrcodeTrans.refno, null, null) - ?: throw InternalError("未找到refno") + ?: throw InternalServerError("未找到refno") } //2. 初始化交易流水 @@ -181,4 +184,34 @@ class ConsumePayServiceImpl : ConsumePayService { 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, + MoneyUtil.FenToYuan(param.amount), transactionService, param.billno, param.shopaccno) + } ?: throw BadRequestError("未找到refno") +} + + override fun queryRefundRetry(billno: String, shopaccno: String): TTransactionMain? { + return transactionMainDao.findByBillno(billno, shopaccno) + } + + override fun queryRefundDtl(refno: String): TRefundDtl? { + return refundDtlDao.findByRefno(refno) + } } \ No newline at end of file diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_service_impl.kt index 82ae2cc6..22fa55a2 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_service_impl.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/transaction_service_impl.kt @@ -2,20 +2,28 @@ package com.supwisdom.dlpay.api.service.impl 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 @@ -39,6 +47,9 @@ class TransactionServiceImpl : TransactionService { @Autowired private lateinit var qrcodePayTransDao: QrcodePayTransDao + @Autowired + private lateinit var refundDtlDao: RefundDtlDao + /// 公共函数部分 private fun preCheck(builder: TransactionBuilder) { @@ -286,25 +297,11 @@ class TransactionServiceImpl : TransactionService { override fun wip(refno: String): TTransactionMain { val transaction = transactionMainDao.findByRefnoForUpdate(refno) - ?: throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>参考号错误") - - if (transaction.status != TradeDict.DTL_STATUS_INIT - && transaction.status != TradeDict.DTL_STATUS_WIP) { - throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>状态错误") - } - - updateRecordStatus(transaction, TradeDict.DTL_STATUS_WIP) - transactionMainDao.save(transaction) - return transaction - } - - override fun qrcodeWip(billno: String,transaction:TTransactionMain): TTransactionMain { - val qrcodeTrans = qrcodePayTransDao.findByQrcodePayTransForUpdate(transaction.outId, systemUtilService.sysdatetime.hostdate, - billno, TenantContextHolder.getContext().tenant.id) - ?: throw InternalError("未找到billno") + ?: throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>参考号错误") if (transaction.status != TradeDict.DTL_STATUS_INIT) { - throw InternalError("流水<${transaction.refno}>状态错误") + throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<${transaction.refno}>状态错误") } + updateRecordStatus(transaction, TradeDict.DTL_STATUS_WIP) transactionMainDao.save(transaction) return transaction @@ -317,33 +314,6 @@ class TransactionServiceImpl : TransactionService { override fun fail(refno: String, remark: String): TTransactionMain { val transaction = transactionMainDao.findByRefnoForUpdate(refno) ?: throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>参考号错误") - - val errorStatus = setOf(TradeDict.DTL_STATUS_SUCCESS, TradeDict.DTL_STATUS_CANCEL) - if (transaction.status in errorStatus) { - throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>状态错误") - } - - updateRecordStatus(transaction, TradeDict.DTL_STATUS_FAIL) - if (transaction.person) { - transaction.personDtl.remark = remark - } - if (transaction.shop) { - transaction.shopDtl.remark = remark - } - if (transaction.reverseType != TradeDict.REVERSE_FLAG_NONE) { - val originTrans = transactionMainDao.findByRefnoNoLock(transaction.reverseRefno ?: "") - ?: throw TransactionProcessException(TradeErrorCode.BUSINESS_DEAL_ERROR, - "系统异常,无法处理退款流水") - originTrans.reverseFlag = TradeDict.REVERSE_FLAG_NONE - transactionMainDao.save(originTrans) - } - transaction.endTime = systemUtilService.sysdatetime.sysdate - transactionMainDao.save(transaction) - return transaction - } - - override fun qrcodeFail(refno: String, remark: String, transaction: TTransactionMain): TTransactionMain { - val errorStatus = setOf(TradeDict.DTL_STATUS_SUCCESS, TradeDict.DTL_STATUS_CANCEL) if (transaction.status in errorStatus) { throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>状态错误") @@ -400,38 +370,6 @@ class TransactionServiceImpl : TransactionService { override fun success(refno: String, sourcetypeRefno: String, accdateUpdate: Boolean?): TTransactionMain { val transaction = transactionMainDao.findByRefnoForUpdate(refno) ?: throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "参考号<$refno>错误,流水不存在") - - val errorStatus = setOf(TradeDict.DTL_STATUS_SUCCESS, TradeDict.DTL_STATUS_CANCEL) - if (transaction.status in errorStatus) { - throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>状态错误,流水已结束") - } - transaction.status = TradeDict.DTL_STATUS_SUCCESS - if (true == accdateUpdate) { - transaction.accdate = systemUtilService.accdate - } - transactionOnSuccess(transaction, sourcetypeRefno, false) - - if (transaction.reverseType != TradeDict.REVERSE_FLAG_NONE) { - val originTrans = transactionMainDao.findByRefnoNoLock(transaction.reverseRefno ?: "") - ?: throw TransactionProcessException(TradeErrorCode.BUSINESS_DEAL_ERROR, - "系统异常,无法处理退款流水") - originTrans.reverseFlag = transaction.reverseType - if (originTrans.person) { - originTrans.personDtl.reverseFlag = originTrans.reverseFlag - originTrans.personDtl.reverseAmount = originTrans.refundAmount - } - if (originTrans.shop) { - originTrans.shopDtl.reverseFlag = originTrans.reverseFlag - } - transactionMainDao.save(originTrans) - } - - transaction.endTime = systemUtilService.sysdatetime.sysdate - transactionMainDao.save(transaction) - return transaction - } - - override fun qrcodeSuccess(refno: String, sourcetypeRefno: String, transaction: TTransactionMain, accdateUpdate: Boolean?): TTransactionMain { val errorStatus = setOf(TradeDict.DTL_STATUS_SUCCESS, TradeDict.DTL_STATUS_CANCEL) if (transaction.status in errorStatus) { throw TransactionProcessException(TradeErrorCode.TRANSACTION_IS_FINISHED, "流水<$refno>状态错误,流水已结束") @@ -443,9 +381,9 @@ class TransactionServiceImpl : TransactionService { 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 @@ -472,10 +410,13 @@ class TransactionServiceImpl : TransactionService { ////////////////////////////////////////////////////////////////// // 回退业务接口,包括撤销和退款 - 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, "原始流水为撤销或退款流水,不能再被退款") @@ -485,24 +426,81 @@ class TransactionServiceImpl : TransactionService { 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}>有一笔正在处理中的退款请求") + } + // TODO: init状态去查询一次? + 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.toString()) + + BigDecimal(lastRefundDtl.amount.toString())).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, + "原流水的退款或撤销状态异常") } } - val transaction = doReverseProcess(originTrans, builder) - originTrans.reverseFlag = TradeDict.REVERSE_FLAG_WIP - originTrans.refundAmount = refundAmount + if (refundAmount > availableAmount) { + throw BadRequestError("退款金额不能超过原流水剩余金额") + } + + 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.toString()) - BigDecimal(refundAmount.toString())).toDouble() + this.seqno = seqno + this.outid = shopaccno + this.outtradeno = billno + this.tenantid = tenantid + this.tradeflag = transaction.personDtl.tradeflag + } + refundDtlDao.save(refundDtl) + return transaction } @@ -525,9 +523,9 @@ class TransactionServiceImpl : TransactionService { } 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 } @@ -549,11 +547,11 @@ class TransactionServiceImpl : TransactionService { } } - 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" @@ -561,7 +559,7 @@ class TransactionServiceImpl : TransactionService { } 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" @@ -570,7 +568,7 @@ class TransactionServiceImpl : TransactionService { 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()) { @@ -584,14 +582,14 @@ class TransactionServiceImpl : TransactionService { 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)) } } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_service.kt index dd9dff40..c94fc405 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_service.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/transaction_service.kt @@ -22,24 +22,15 @@ interface TransactionService { @Transactional fun wip(builder: TransactionBuilder): TTransactionMain - @Transactional - fun qrcodeWip(billno: String, transaction: TTransactionMain): TTransactionMain - @Transactional fun fail(refno: String): TTransactionMain @Transactional fun fail(refno: String, remark: String): TTransactionMain - @Transactional - fun qrcodeFail(refno: String, remark: String, transaction: TTransactionMain): TTransactionMain - @Transactional fun success(refno: String, sourcetypeRefno: String, accdateUpdate: Boolean? = true): TTransactionMain - @Transactional - fun qrcodeSuccess(refno: String, sourcetypeRefno: String, transaction: TTransactionMain, accdateUpdate: Boolean? = true): TTransactionMain - @Transactional fun success(refno: String): TTransactionMain @@ -50,7 +41,7 @@ interface TransactionService { * 退款类业务 */ @Transactional - fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder): TTransactionMain + fun refundInit(originRefno: String, refundAmount: Double, builder: TransactionBuilder, billno: String?, shopaccno: String?): TTransactionMain /** * 撤销业务 @@ -102,10 +93,6 @@ class TransactionServiceProxy { return transactionService.wip(refno) } - fun qrcodeWip(billno: String, transaction: TTransactionMain): TTransactionMain { - return transactionService.qrcodeWip(billno, transaction) - } - fun fail(refno: String): TTransactionMain { return transactionService.fail(refno) } @@ -114,10 +101,6 @@ class TransactionServiceProxy { return transactionService.fail(refno, remark) } - fun qrcodeFail(refno: String, remark: String, transaction: TTransactionMain): TTransactionMain { - return transactionService.qrcodeFail(refno, remark, transaction) - } - fun success(refno: String, sourcetypeRefno: String, accdateUpdate: Boolean? = true): TTransactionMain { return transactionService.success(refno, sourcetypeRefno, accdateUpdate).also { if (it.shop) { @@ -133,24 +116,9 @@ class TransactionServiceProxy { } } - fun qrcodeSuccess(refno: String, sourcetypeRefno: String, transaction: TTransactionMain, accdateUpdate: Boolean? = true): TTransactionMain { - return transactionService.qrcodeSuccess(refno, sourcetypeRefno, transaction, accdateUpdate).also { - if (it.shop) { - shopAccBalanceAsyncTask.updateShopBalance(TenantContextHolder.getContext().tenant, it.shopDtl) - } - - if (it.person && !it.personDtl.userid.isNullOrEmpty()) { - kafkaSendMsgService.sendJpushMessage(it.personDtl.userid, - "交易提醒", - "你有一笔${it.personDtl.amount}元的支出,点击查看详情", - it.refno, mutableMapOf(), it.tenantid) - } - } - } - fun success(refno: String): TTransactionMain { - return success(refno, "") + return success(refno, "", null) } fun cancel(originRefno: String, builder: TransactionBuilder, confirm: Boolean): TTransactionMain { @@ -161,8 +129,8 @@ class TransactionServiceProxy { 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) } @@ -185,10 +153,6 @@ class TransactionServiceProxy { return transactionMainDao.findByRefno(refno) } - fun findTransactionByRefnoForUpdate(refno: String): TTransactionMain { - return transactionMainDao.findByRefnoForUpdate(refno) - } - fun findByOutTradeNo(refno: String): TTransactionMain? { return transactionMainDao.findByOutTradeNo(refno) } diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt index 80682b9e..d607f1cc 100644 --- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt +++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/transaction_builder.kt @@ -435,13 +435,13 @@ class TransactionBuilder { 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 { diff --git a/payapi/src/test/kotlin/com/supwisdom/dlpay/controller/security_controller_test.kt b/payapi/src/test/kotlin/com/supwisdom/dlpay/controller/security_controller_test.kt index 0d4f8085..df2336d8 100644 --- a/payapi/src/test/kotlin/com/supwisdom/dlpay/controller/security_controller_test.kt +++ b/payapi/src/test/kotlin/com/supwisdom/dlpay/controller/security_controller_test.kt @@ -125,7 +125,7 @@ class SecurityControllerTest : MvcBaseTest() { @Test fun testHMACSHA256() { - val token = "ddCUUHAF/OOBTAZq" + val token = "SIc4iIpjMphmZOLg" val secret = "dc1d26c0d43e442588092c8d45c21bce" val jwt = HmacUtil.HMACSHA256(token, secret) println(jwt)