diff --git a/wfc-modules/wfc-payment/pom.xml b/wfc-modules/wfc-payment/pom.xml index 0eba6f3..f799cb9 100644 --- a/wfc-modules/wfc-payment/pom.xml +++ b/wfc-modules/wfc-payment/pom.xml @@ -11,12 +11,10 @@ wfc-modules-payment - 1.0.0 + 1.0.1 - - wfc-modules-payment - + wfc-modules-payment @@ -96,6 +94,20 @@ 4.11.0.ALL + + + com.egzosn + pay-java-union + 2.14.7 + + + + + com.egzosn + pay-java-web-support + 2.14.7 + + org.springdoc @@ -103,6 +115,12 @@ 1.6.14 + + javax.persistence + javax.persistence-api + 2.2 + + diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/WfcPaymentApplication.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/WfcPaymentApplication.java index af58d02..9478241 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/WfcPaymentApplication.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/WfcPaymentApplication.java @@ -38,12 +38,12 @@ public class WfcPaymentApplication public static void main(String[] args) { SpringApplication.run(WfcPaymentApplication.class, args); - logger.info("(♥◠‿◠)ノ゙ Payment module startup ლ(´ڡ`ლ)゙ \n"); + logger.info("(♥◠‿◠)ノ゙ Payment module startup successfully ლ(´ڡ`ლ)゙ \n"); } @PostConstruct public void printConfig() { - System.out.println("CCPay URL: " + ccpayConfig.getUrl()); + System.out.println("CCPay PaymentURL: " + ccpayConfig.getPaymentUrl()); System.out.println("CCPay API Key: " + ccpayConfig.getApiKey()); System.out.println("CCPay Merchant ID: " + ccpayConfig.getMerchantId()); System.out.println("CCPay Currency: " + ccpayConfig.getCurrency()); diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/alipay/controller/AlipayController.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/alipay/controller/AlipayController.java index 2172313..cba3878 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/alipay/controller/AlipayController.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/alipay/controller/AlipayController.java @@ -4,7 +4,6 @@ import com.alipay.api.AlipayApiException; import com.alipay.api.response.AlipayTradeQueryResponse; import com.alipay.api.response.AlipayTradeCloseResponse; import com.alipay.api.response.AlipayTradeRefundResponse; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -23,13 +22,9 @@ import lombok.AllArgsConstructor; @AllArgsConstructor public class AlipayController { - @Autowired private final IAlipayPaymentService alipayPaymentService; - @Autowired private final IAlipayQueryOrderService alipayQueryService; - @Autowired private final IAlipayTradeCloseService alipayCloseService; - @Autowired private final IAlipayRefundService alipayRefundService; @Operation(summary = "Create Alipay order") diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/config/CcpayConfig.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/config/CcpayConfig.java index 5de8529..f407466 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/config/CcpayConfig.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/config/CcpayConfig.java @@ -8,10 +8,15 @@ import org.springframework.context.annotation.Configuration; @Configuration @ConfigurationProperties(prefix = "ccpay") public class CcpayConfig { - private String url; + private String paymentUrl; + private String tokenUrl; + private String refundUrl; + private String queryUrl; private String apiKey; private String merchantId; private String currency; private int timeout; private String callbackUrl; + private String clientId; + private String clientSecret; } \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/controller/CcpayController.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/controller/CcpayController.java index 6e3d79a..e5d5b06 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/controller/CcpayController.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/controller/CcpayController.java @@ -5,28 +5,61 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.wfc.payment.ccpay.model.PaymentRequest; import org.wfc.payment.ccpay.model.PaymentResponse; -import org.wfc.payment.ccpay.service.ICcpayPaymentService; -import org.wfc.common.core.constant.HttpStatus; +import org.wfc.payment.ccpay.model.UCreditCardToken; +import org.wfc.payment.ccpay.model.RefundResponse; +import org.wfc.payment.ccpay.model.RefundRequest; +import org.wfc.payment.ccpay.service.IUCreditCardPaymentService; +import org.wfc.payment.ccpay.service.IUCreditCardTokenService; /** - * Credit card pay controller + * Credit card payment controller * */ @RestController @RequestMapping("/ccpay") public class CcpayController { + private final IUCreditCardPaymentService paymentService; + private final IUCreditCardTokenService tokenService; + @Autowired - private ICcpayPaymentService ccpayService; + public CcpayController(IUCreditCardPaymentService paymentService, IUCreditCardTokenService tokenService) { + this.paymentService = paymentService; + this.tokenService = tokenService; + } @PostMapping("/payOrder") - public ResponseEntity processPayment(@RequestBody PaymentRequest paymentRequest) { - // 调用支付服务处理支付请求 - PaymentResponse paymentResult = ccpayService.processPayment(paymentRequest); - if (paymentResult.isSuccess()) { - return ResponseEntity.ok("Payment successful"); + public ResponseEntity processPayment(@RequestBody PaymentRequest paymentRequest) { + // 检查用户是否已有支付令牌 + UCreditCardToken token = tokenService.getTokenByUserId(paymentRequest.getUserId()); + if (token != null) { + // 使用支付令牌进行支付 + paymentRequest.setToken(token.getToken()); } else { - return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Payment failed"); + // 用户首次支付,获取支付令牌并存储 + String newToken = paymentService.getPaymentToken(paymentRequest); + token = new UCreditCardToken(); + token.setUserId(paymentRequest.getUserId()); + token.setToken(newToken); + token.setCardLastFour(paymentRequest.getCardLastFour()); + token.setCardType(paymentRequest.getCardType()); + tokenService.saveToken(token); + paymentRequest.setToken(newToken); } + + PaymentResponse processedPayment = paymentService.processPayment(paymentRequest); + return ResponseEntity.ok(processedPayment); } + + @GetMapping("/queryOrder/{orderId}") + public ResponseEntity queryOrder(@PathVariable Long orderId) { + PaymentResponse paymentResponse = paymentService.queryOrder(orderId); + return ResponseEntity.ok(paymentResponse); + } + + @PostMapping("/refundOrder") + public ResponseEntity refundOrder(@RequestBody RefundRequest refundRequest) { + RefundResponse refundResponse = paymentService.refundOrder(refundRequest); + return ResponseEntity.ok(refundResponse); + } } \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/exception/RuntimeException.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/exception/RuntimeException.java new file mode 100644 index 0000000..9fe42a4 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/exception/RuntimeException.java @@ -0,0 +1,12 @@ +package org.wfc.payment.ccpay.exception; + +public class RuntimeException extends Exception { + + public RuntimeException(String message) { + super(message); + } + + public RuntimeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardPaymentMapper.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardPaymentMapper.java new file mode 100644 index 0000000..9b95fcb --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardPaymentMapper.java @@ -0,0 +1,15 @@ +package org.wfc.payment.ccpay.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.wfc.payment.ccpay.model.UCreditCardPayment; + +@Mapper +public interface UCreditCardPaymentMapper { + + void insertPayment(UCreditCardPayment payment); + + UCreditCardPayment selectPaymentById(@Param("id") Long id); + + // 其他需要的方法 +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardTokenMapper.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardTokenMapper.java new file mode 100644 index 0000000..4390438 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/mapper/UCreditCardTokenMapper.java @@ -0,0 +1,13 @@ +package org.wfc.payment.ccpay.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.wfc.payment.ccpay.model.UCreditCardToken; + +@Mapper +public interface UCreditCardTokenMapper { + + void insertToken(UCreditCardToken token); + + UCreditCardToken selectTokenByUserId(@Param("userId") Long userId); +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/PaymentRequest.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/PaymentRequest.java index 091681c..49a43a3 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/PaymentRequest.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/PaymentRequest.java @@ -4,9 +4,37 @@ import lombok.Data; @Data public class PaymentRequest { + private Long userId; private String cardNumber; + private String cardLastFour; private String cardHolderName; private String expirationDate; private String cvv; private double amount; -} \ No newline at end of file + private String token; + private String cardType; + + public Long getUserId() { + return userId; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public void setToken(String token) { + this.token = token; + } + + public void setCardLastFour() { + this.cardLastFour = cardNumber.substring(cardNumber.length() - 4); + } + + public String getCardLastFour() { + return cardLastFour; + } + + public String getCardType() { + return cardType; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundRequest.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundRequest.java new file mode 100644 index 0000000..bd22139 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundRequest.java @@ -0,0 +1,37 @@ +package org.wfc.payment.ccpay.model; + +public class RefundRequest { + private String transactionId; + private double amount; + private String reason; + + public RefundRequest(String transactionId, double amount, String reason) { + this.transactionId = transactionId; + this.amount = amount; + this.reason = reason; + } + + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public double getAmount() { + return amount; + } + + public void setAmount(double amount) { + this.amount = amount; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundResponse.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundResponse.java new file mode 100644 index 0000000..1431585 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/RefundResponse.java @@ -0,0 +1,27 @@ +package org.wfc.payment.ccpay.model; + +public class RefundResponse { + private String status; + private String message; + + public RefundResponse(String status, String message) { + this.status = status; + this.message = message; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardPayment.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardPayment.java new file mode 100644 index 0000000..cab7d0c --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardPayment.java @@ -0,0 +1,51 @@ +package org.wfc.payment.ccpay.model; + +import javax.persistence.*; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Entity +@Table(name = "u_credit_card_payment") +public class UCreditCardPayment { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "transaction_id", nullable = false) + private String transactionId; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "order_id", nullable = false) + private Long orderId; + + @Column(name = "amount", nullable = false) + private BigDecimal amount; + + @Column(name = "currency", nullable = false) + private String currency; + + @Column(name = "status", nullable = false) + private String status; + + @Column(name = "payment_time", nullable = false) + private LocalDateTime paymentTime; + + @Column(name = "card_type") + private String cardType; + + @Column(name = "card_holder_name") + private String cardHolderName; + + @Column(name = "billing_address") + private String billingAddress; + + @Column(name = "card_last_four") + private String cardLastFour; + + @Column(name = "gateway_response") + private String gatewayResponse; + + // Getters and Setters +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardToken.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardToken.java new file mode 100644 index 0000000..fd4c402 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/model/UCreditCardToken.java @@ -0,0 +1,48 @@ +package org.wfc.payment.ccpay.model; + +import javax.persistence.*; +import java.time.LocalDateTime; + +@Entity +@Table(name = "u_credit_card_token") +public class UCreditCardToken { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "token", nullable = false) + private String token; + + @Column(name = "card_last_four") + private String cardLastFour; + + @Column(name = "card_type") + private String cardType; + + @Column(name = "created_at", nullable = false) + private LocalDateTime createdAt; + + public void setUserId(Long userId) { + this.userId = userId; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public void setCardLastFour(String cardLastFour) { + this.cardLastFour = cardLastFour; + } + + public void setCardType(String cardType) { + this.cardType = cardType; + } +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/ICcpayPaymentService.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/ICcpayPaymentService.java deleted file mode 100644 index d9adc22..0000000 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/ICcpayPaymentService.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.wfc.payment.ccpay.service; - -import org.wfc.payment.ccpay.model.PaymentRequest; -import org.wfc.payment.ccpay.model.PaymentResponse; - -public interface ICcpayPaymentService { - PaymentResponse processPayment(PaymentRequest paymentRequest); -} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardPaymentService.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardPaymentService.java new file mode 100644 index 0000000..3b6a469 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardPaymentService.java @@ -0,0 +1,23 @@ +package org.wfc.payment.ccpay.service; + +import org.wfc.payment.ccpay.model.PaymentRequest; +import org.wfc.payment.ccpay.model.PaymentResponse; +import org.wfc.payment.ccpay.model.RefundRequest; +import org.wfc.payment.ccpay.model.RefundResponse; +import org.wfc.payment.ccpay.model.UCreditCardPayment; + +public interface IUCreditCardPaymentService { + + PaymentResponse processPayment(PaymentRequest paymentRequest); + + PaymentResponse queryOrder(Long orderId); + + RefundResponse refundOrder(RefundRequest refundRequest); + + void savePayment(UCreditCardPayment payment); + + UCreditCardPayment getPaymentById(Long id); + + String getPaymentToken(PaymentRequest paymentRequest); + +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardTokenService.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardTokenService.java new file mode 100644 index 0000000..98b2432 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/IUCreditCardTokenService.java @@ -0,0 +1,13 @@ +package org.wfc.payment.ccpay.service; + +import org.wfc.payment.ccpay.model.PaymentRequest; +import org.wfc.payment.ccpay.model.UCreditCardToken; + +public interface IUCreditCardTokenService { + + void saveToken(UCreditCardToken token); + + UCreditCardToken getTokenByUserId(Long userId); + + String getPaymentToken(PaymentRequest paymentRequest); +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/CcpayPaymentServiceImpl.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/CcpayPaymentServiceImpl.java deleted file mode 100644 index 106a67a..0000000 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/CcpayPaymentServiceImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.wfc.payment.ccpay.service.impl; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.web.client.RestTemplate; -import org.wfc.payment.ccpay.config.CcpayConfig; -import org.wfc.payment.ccpay.model.PaymentRequest; -import org.wfc.payment.ccpay.model.PaymentResponse; -import org.wfc.payment.ccpay.service.ICcpayPaymentService; - -import java.util.HashMap; -import java.util.Map; - -@Service -public class CcpayPaymentServiceImpl implements ICcpayPaymentService { - - @Autowired - private CcpayConfig ccpayConfig; - - @Override - public PaymentResponse processPayment(PaymentRequest paymentRequest) { - RestTemplate restTemplate = new RestTemplate(); - PaymentResponse paymentResponse = new PaymentResponse(); - - try { - // 设置请求头 - Map headers = new HashMap<>(); - headers.put("Authorization", "Bearer " + ccpayConfig.getApiKey()); - headers.put("Content-Type", "application/json"); - - // 设置请求体 - Map requestBody = new HashMap<>(); - requestBody.put("merchantId", ccpayConfig.getMerchantId()); - requestBody.put("currency", ccpayConfig.getCurrency()); - requestBody.put("amount", paymentRequest.getAmount()); - requestBody.put("cardNumber", paymentRequest.getCardNumber()); - requestBody.put("cardHolderName", paymentRequest.getCardHolderName()); - requestBody.put("expirationDate", paymentRequest.getExpirationDate()); - requestBody.put("cvv", paymentRequest.getCvv()); - requestBody.put("callbackUrl", ccpayConfig.getCallbackUrl()); - - // 调用第三方支付网关的 API 进行支付处理 - PaymentResponse response = restTemplate.postForObject(ccpayConfig.getUrl(), requestBody, - PaymentResponse.class); - - if (response != null && response.isSuccess()) { - paymentResponse.setSuccess(true); - paymentResponse.setMessage("Payment successful"); - paymentResponse.setTransactionId(response.getTransactionId()); - } else { - paymentResponse.setSuccess(false); - paymentResponse.setMessage("Payment failed"); - } - } catch (Exception e) { - paymentResponse.setSuccess(false); - paymentResponse.setMessage("Error occurred while processing payment: " + e.getMessage()); - } - - return paymentResponse; - } -} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardPaymentServiceImpl.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardPaymentServiceImpl.java new file mode 100644 index 0000000..4e9b031 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardPaymentServiceImpl.java @@ -0,0 +1,143 @@ +package org.wfc.payment.ccpay.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; +import org.springframework.core.ParameterizedTypeReference; +import org.wfc.payment.ccpay.config.CcpayConfig; +import org.wfc.payment.ccpay.mapper.UCreditCardPaymentMapper; +import org.wfc.payment.ccpay.model.PaymentRequest; +import org.wfc.payment.ccpay.model.PaymentResponse; +import org.wfc.payment.ccpay.model.RefundRequest; +import org.wfc.payment.ccpay.model.RefundResponse; +import org.wfc.payment.ccpay.model.UCreditCardPayment; +import org.wfc.payment.ccpay.service.IUCreditCardPaymentService; + + +import java.util.HashMap; +import java.util.Map; + +@Service +public class UCreditCardPaymentServiceImpl implements IUCreditCardPaymentService { + + private final UCreditCardPaymentMapper paymentMapper; + private final CcpayConfig ccpayConfig; + + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + private static final String APPLICATION_JSON = "application/json"; + + @Autowired + public UCreditCardPaymentServiceImpl(UCreditCardPaymentMapper paymentMapper, CcpayConfig ccpayConfig) { + this.paymentMapper = paymentMapper; + this.ccpayConfig = ccpayConfig; + } + + @Override + public void savePayment(UCreditCardPayment payment) { + paymentMapper.insertPayment(payment); + } + + @Override + public UCreditCardPayment getPaymentById(Long id) { + return paymentMapper.selectPaymentById(id); + } + + @Override + public String getPaymentToken(PaymentRequest paymentRequest) { + RestTemplate restTemplate = new RestTemplate(); + String authUrl = ccpayConfig.getTokenUrl(); + + HttpHeaders headers = new HttpHeaders(); + + headers.set(CONTENT_TYPE_HEADER, "application/x-www-form-urlencoded"); + + // 设置请求体 + Map requestBody = new HashMap<>(); + requestBody.put("grant_type", "client_credentials"); + requestBody.put("client_id", ccpayConfig.getClientId()); + requestBody.put("client_secret", ccpayConfig.getClientSecret()); + + // 调用第三方支付网关的 API 获取 token + ResponseEntity> response = restTemplate.exchange( + authUrl, HttpMethod.POST, new HttpEntity<>(requestBody, headers), + new ParameterizedTypeReference>() {}); + + // 从响应中提取 token + Map responseBody = response.getBody(); + if (responseBody != null && responseBody.containsKey("access_token")) { + return (String) responseBody.get("access_token"); + } else { + throw new IllegalStateException("Failed to obtain access token from third-party gateway"); + } + } + + @Override + public PaymentResponse processPayment(PaymentRequest paymentRequest) { + RestTemplate restTemplate = new RestTemplate(); + PaymentResponse paymentResponse = new PaymentResponse(); + + try { + HttpHeaders headers = new HttpHeaders(); + headers.set(CONTENT_TYPE_HEADER, APPLICATION_JSON); + + // 设置请求体 + Map requestBody = new HashMap<>(); + requestBody.put("merchantId", ccpayConfig.getMerchantId()); + requestBody.put("currency", ccpayConfig.getCurrency()); + requestBody.put("amount", paymentRequest.getAmount()); + requestBody.put("cardNumber", paymentRequest.getCardNumber()); + requestBody.put("cardHolderName", paymentRequest.getCardHolderName()); + requestBody.put("expirationDate", paymentRequest.getExpirationDate()); + requestBody.put("cvv", paymentRequest.getCvv()); + requestBody.put("callbackUrl", ccpayConfig.getCallbackUrl()); + + // 调用第三方支付网关的 API 进行支付处理 + PaymentResponse response = restTemplate.postForObject(ccpayConfig.getPaymentUrl(), requestBody, + PaymentResponse.class); + + if (response != null && response.isSuccess()) { + paymentResponse.setSuccess(true); + paymentResponse.setMessage("Payment successful"); + paymentResponse.setTransactionId(response.getTransactionId()); + } else { + paymentResponse.setSuccess(false); + paymentResponse.setMessage("Payment failed"); + } + } catch (Exception e) { + paymentResponse.setSuccess(false); + paymentResponse.setMessage("Error occurred while processing payment: " + e.getMessage()); + } + + return paymentResponse; + } + + @Override + public PaymentResponse queryOrder(Long orderId) { + // 实现查询订单逻辑 + // 例如,调用第三方支付网关的查询订单接口 + RestTemplate restTemplate = new RestTemplate(); + String queryUrl = ccpayConfig.getQueryUrl() + "/" + orderId; + + ResponseEntity response = restTemplate.getForEntity(queryUrl, PaymentResponse.class); + return response.getBody(); + } + + @Override + public RefundResponse refundOrder(RefundRequest refundRequest) { + // 实现退款逻辑 + // 例如,调用第三方支付网关的退款接口 + RestTemplate restTemplate = new RestTemplate(); + String refundUrl = ccpayConfig.getRefundUrl(); + HttpHeaders headers = new HttpHeaders(); + headers.set(CONTENT_TYPE_HEADER, APPLICATION_JSON); + + HttpEntity requestEntity = new HttpEntity<>(refundRequest, headers); + ResponseEntity response = restTemplate.postForEntity(refundUrl, requestEntity, + RefundResponse.class); + return response.getBody(); + } +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardTokenServiceImpl.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardTokenServiceImpl.java new file mode 100644 index 0000000..62af752 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/ccpay/service/impl/UCreditCardTokenServiceImpl.java @@ -0,0 +1,34 @@ +package org.wfc.payment.ccpay.service.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.wfc.payment.ccpay.mapper.UCreditCardTokenMapper; +import org.wfc.payment.ccpay.model.PaymentRequest; +import org.wfc.payment.ccpay.model.UCreditCardToken; +import org.wfc.payment.ccpay.service.IUCreditCardTokenService; + +@Service +public class UCreditCardTokenServiceImpl implements IUCreditCardTokenService { + + private final UCreditCardTokenMapper tokenMapper; + + @Autowired + public UCreditCardTokenServiceImpl(UCreditCardTokenMapper tokenMapper) { + this.tokenMapper = tokenMapper; + } + + @Override + public void saveToken(UCreditCardToken token) { + tokenMapper.insertToken(token); + } + + @Override + public UCreditCardToken getTokenByUserId(Long userId) { + return tokenMapper.selectTokenByUserId(userId); + } + + @Override + public String getPaymentToken(PaymentRequest paymentRequest) { + return paymentRequest.getToken(); + } +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayConfig.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayConfig.java new file mode 100644 index 0000000..2671f0d --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayConfig.java @@ -0,0 +1,59 @@ +package org.wfc.payment.unionpay.config; + +import lombok.Data; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties(prefix = "unionpay") +public class UnionPayConfig { + + private String acpRootCert; + private String acpMiddleCert; + private String keyPrivateCert; + private String keyPrivateCertPwd; + private Boolean certSign; + + // Getters and setters + public String getAcpRootCert() { + return acpRootCert; + } + + public void setAcpRootCert(String acpRootCert) { + this.acpRootCert = acpRootCert; + } + + public String getAcpMiddleCert() { + return acpMiddleCert; + } + + public void setAcpMiddleCert(String acpMiddleCert) { + this.acpMiddleCert = acpMiddleCert; + } + + public String getKeyPrivateCert() { + return keyPrivateCert; + } + + public void setKeyPrivateCert(String keyPrivateCert) { + this.keyPrivateCert = keyPrivateCert; + } + + public String getKeyPrivateCertPwd() { + return keyPrivateCertPwd; + } + + public void setKeyPrivateCertPwd(String keyPrivateCertPwd) { + this.keyPrivateCertPwd = keyPrivateCertPwd; + } + public Boolean getCertSign() { + return certSign; + } + + public void setCertSign(Boolean certSign) { + this.certSign = certSign; + } + +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayServiceConfig.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayServiceConfig.java new file mode 100644 index 0000000..1d078b8 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/config/UnionPayServiceConfig.java @@ -0,0 +1,39 @@ +package org.wfc.payment.unionpay.config; + +import com.egzosn.pay.union.api.UnionPayConfigStorage; +import com.egzosn.pay.union.api.UnionPayService; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.common.bean.CertStoreType; + +@Configuration +public class UnionPayServiceConfig { + + private final UnionPayConfig unionPayConfig; + + public UnionPayServiceConfig(UnionPayConfig unionPayConfig) { + this.unionPayConfig = unionPayConfig; + } + + @Bean + public UnionPayService unionPayService() { + UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); + unionPayConfigStorage.setMerId("700000000000001"); + unionPayConfigStorage.setCertSign(unionPayConfig.getCertSign()); + unionPayConfigStorage.setAcpMiddleCert(unionPayConfig.getAcpMiddleCert()); + unionPayConfigStorage.setAcpRootCert(unionPayConfig.getAcpRootCert()); + unionPayConfigStorage.setKeyPrivateCert(unionPayConfig.getKeyPrivateCert()); + unionPayConfigStorage.setKeyPrivateCertPwd(unionPayConfig.getKeyPrivateCertPwd()); + unionPayConfigStorage.setCertStoreType(CertStoreType.PATH); // 使用文件存储方式 + + unionPayConfigStorage.setReturnUrl("http://wfc-gateway:8080/payment/unionpay/payBack.json"); + unionPayConfigStorage.setNotifyUrl("http://wfc-gateway:8080/payment/unionpay/payBack.json"); + unionPayConfigStorage.setSignType(SignUtils.RSA2.name()); + unionPayConfigStorage.setPayType("UNION_PAY"); + unionPayConfigStorage.setInputCharset("UTF-8"); + unionPayConfigStorage.setTest(true); + + return new UnionPayService(unionPayConfigStorage); + } +} \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/controller/UnionPayController.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/controller/UnionPayController.java new file mode 100644 index 0000000..9ccc74a --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/controller/UnionPayController.java @@ -0,0 +1,294 @@ + +package org.wfc.payment.unionpay.controller; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import javax.annotation.PostConstruct; +import javax.imageio.ImageIO; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.wfc.payment.unionpay.model.QueryOrder; + +import static com.egzosn.pay.union.bean.UnionTransactionType.WEB; + +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.PayOrder; +import com.egzosn.pay.common.bean.RefundOrder; +import com.egzosn.pay.common.http.HttpConfigStorage; +import com.egzosn.pay.common.util.sign.SignTextUtils; +import com.egzosn.pay.common.util.sign.SignUtils; +import com.egzosn.pay.union.api.UnionPayConfigStorage; +import com.egzosn.pay.union.api.UnionPayService; +import com.egzosn.pay.union.bean.UnionRefundResult; +import com.egzosn.pay.union.bean.UnionTransactionType; +import com.egzosn.pay.common.bean.AssistOrder; +import com.egzosn.pay.web.support.HttpRequestNoticeParams; +import org.wfc.payment.unionpay.config.UnionPayConfig; + +/** + * Union pay controller + * + */ +@RestController +@RequestMapping("unionpay") +public class UnionPayController { + + private UnionPayService unionPayService = null; + private final UnionPayConfig unionPayConfig; + + @Autowired + public UnionPayController(UnionPayService unionPayService, UnionPayConfig unionPayConfig) { + this.unionPayService = unionPayService; + this.unionPayConfig = unionPayConfig; + } + + @PostConstruct + public void init() { + // UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); + // unionPayConfigStorage.setMerId("700000000000001"); + // //是否为证书签名 + // unionPayConfigStorage.setCertSign(true); + // //中级证书路径 + // unionPayConfigStorage.setAcpMiddleCert(unionPayConfig.getAcpMiddleCert()); + // //根证书路径 + // unionPayConfigStorage.setAcpRootCert(unionPayConfig.getAcpRootCert()); + // // 私钥证书路径 + // unionPayConfigStorage.setKeyPrivateCert(unionPayConfig.getKeyPrivateCert()); + // //私钥证书对应的密码 + // unionPayConfigStorage.setKeyPrivateCertPwd(unionPayConfig.getKeyPrivateCertPwd()); + // //设置证书对应的存储方式,这里默认为文件地址 + // unionPayConfigStorage.setCertStoreType(CertStoreType.URL); + + + // //前台通知网址 即SDKConstants.param_frontUrl + // unionPayConfigStorage.setReturnUrl("http://wfc-gateway:8080/payment/unionpay/payBack.json"); + // //后台通知地址 即SDKConstants.param_backUrl + // unionPayConfigStorage.setNotifyUrl("http://wfc-gateway:8080/payment/unionpay/payBack.json"); + // //加密方式 + // unionPayConfigStorage.setSignType(SignUtils.RSA2.name()); + // //单一支付可不填 + // unionPayConfigStorage.setPayType("UNION_PAY"); + // unionPayConfigStorage.setInputCharset("UTF-8"); + // //是否为测试账号,沙箱环境 + // unionPayConfigStorage.setTest(true); + // unionPayService = new UnionPayService(unionPayConfigStorage); + + // //请求连接池配置 + // HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); + // //最大连接数 + // httpConfigStorage.setMaxTotal(20); + // //默认的每个路由的最大连接数 + // httpConfigStorage.setDefaultMaxPerRoute(10); + // unionPayService.setRequestTemplateConfigStorage(httpConfigStorage); + + } + + /** + * ---业务实现例子1--- + * 功能:生成自动跳转的Html表单 + * 业务类型(关键字):网关支付(WEB)/手机网页支付,企业网银支付(B2B), + * @param price 金额 + * @return 生成自动跳转的Html表单 + */ + @RequestMapping(value = "toPay.html", produces = "text/html;charset=UTF-8") + public String toPay( BigDecimal price) { + //网关支付(WEB)/手机网页支付 + PayOrder order = new PayOrder("title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", ""), + WEB); + return unionPayService.toPay(order); + } + + /** + * ---业务实现例子2--- + * 功能:获取调起控件的tn号,支付结果等 + * 业务类型:手机控件支付产品(WAP), + * @param price 金额 + * @return 支付结果 + */ + @RequestMapping(value = "toPay.json") + public Map sendHttpRequest( BigDecimal price) { + //手机控件支付产品 + PayOrder order = new PayOrder("title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, UUID.randomUUID().toString().replace("-", "") + ,UnionTransactionType.WAP); + return unionPayService.app(order); + } + + /** + * APP 获取支付预订单信息 + * + * @return 支付预订单信息 + */ + @RequestMapping("app") + public Map app() { + Map data = new HashMap<>(); + data.put("code", 0); + PayOrder order = new PayOrder("title", "摘要", BigDecimal.valueOf(0.01), SignTextUtils.randomStr()); + //App支付 + order.setTransactionType(UnionTransactionType.APP); + data.put("orderInfo", unionPayService.app(order)); + return data; + } + + /** + * 获取二维码图像 APPLY_QR_CODE + * 二维码支付 + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "toQrPay.jpg", produces = "image/jpeg;charset=UTF-8") + public byte[] toWxQrPay( BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ImageIO.write(unionPayService.genQrPay( new PayOrder("title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)), "JPEG", baos); + return baos.toByteArray(); + } + + /** + * 获取二维码地址 + * 二维码支付 + * @param price 金额 + * @return 二维码图像 + * @throws IOException IOException + */ + @RequestMapping(value = "getQrPay.json") + public String getQrPay(BigDecimal price) throws IOException { + //获取对应的支付账户操作工具(可根据账户id) + return unionPayService.getQrPay( new PayOrder("订单title", "摘要", null == price ? BigDecimal.valueOf(0.01) : price, System.currentTimeMillis()+"", UnionTransactionType.APPLY_QR_CODE)); + } + + /** + * 刷卡付,pos主动扫码付款(条码付) CONSUME + * @param authCode 授权码,条码等 + * @param price 金额 + * @return 支付结果 + */ + @RequestMapping(value = "microPay") + public Map microPay(BigDecimal price, String authCode) { + //获取对应的支付账户操作工具(可根据账户id) + //条码付 + PayOrder order = new PayOrder("egan order", "egan order", null == price ? BigDecimal.valueOf(0.01) : price, SignTextUtils.randomStr(), UnionTransactionType.CONSUME); + //设置授权码,条码等 + order.setAuthCode(authCode); + //支付结果 + Map params = unionPayService.microPay(order); + //校验 + if (unionPayService.verify(params)) { + + //支付校验通过后的处理 + //......业务逻辑处理块........ + + + } + //这里开发者自行处理 + return params; + } + + /** + * 支付回调地址 方式一 + * + * 方式二,{@link #payBack(HttpServletRequest)} 是属于简化方式, 试用与简单的业务场景 + * + * @param request 请求 + * @return 是否成功 + * @see #payBack(HttpServletRequest) + * @throws IOException IOException + * @deprecated This method is deprecated and will be removed in future versions. Use {@link #payBack(HttpServletRequest)} instead. + */ + @Deprecated + @RequestMapping(value = "payBackBefore.json") + public String payBackBefore(HttpServletRequest request) throws IOException { + + //获取支付方返回的对应参数 + Map params = unionPayService.getParameter2Map(request.getParameterMap(), request.getInputStream()); + if (null == params) { + return unionPayService.getPayOutMessage("fail", "失败").toMessage(); + } + + //校验 + if (unionPayService.verify(params)) { + //这里处理业务逻辑 + //......业务逻辑处理块........ + return unionPayService.successPayOutMessage(null).toMessage(); + } + + return unionPayService.getPayOutMessage("fail", "失败").toMessage(); + } + /** + * 支付回调地址 + * + * @param request 请求 + * + * @return 是否成功 + * + * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + * + * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBackOld.json") + public String payBackOld(HttpServletRequest request) throws IOException { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return unionPayService.payBack(request.getParameterMap(), request.getInputStream()).toMessage(); + } + /** + * 支付回调地址 + * + * @param request 请求 + * @return 是否成功 + *

+ * 业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看{@link com.egzosn.pay.common.api.PayService#setPayMessageHandler(com.egzosn.pay.common.api.PayMessageHandler)} + *

+ * 如果未设置 {@link com.egzosn.pay.common.api.PayMessageHandler} 那么会使用默认的 {@link com.egzosn.pay.common.api.DefaultPayMessageHandler} + * @throws IOException IOException + */ + @RequestMapping(value = "payBack.json") + public String payBack(HttpServletRequest request) { + //业务处理在对应的PayMessageHandler里面处理,在哪里设置PayMessageHandler,详情查看com.egzosn.pay.common.api.PayService.setPayMessageHandler() + return unionPayService.payBack(new HttpRequestNoticeParams(request)).toMessage(); + } + + + /** + * 查询 + * + * @param order 订单的请求体 + * @return 返回查询回来的结果集,支付方原值返回 + */ + @RequestMapping("query") + public Map query(QueryOrder order) { + return unionPayService.query(new AssistOrder(order.getTradeNo(), order.getOutTradeNo())); + } + + + /** + * 申请退款接口 + * + * @param order 订单的请求体 + * @return 返回支付方申请退款后的结果 + */ + @RequestMapping("refund") + public UnionRefundResult refund(RefundOrder order) { + return unionPayService.refund(order); + } + + + /** + * 下载对账单 + * + * @param order 订单的请求体 + * @return 返回支付方下载对账单的结果 + */ + @RequestMapping("downloadBill") + public Object downloadBill(QueryOrder order) { + return unionPayService.downloadBill(order.getBillDate(), order.getBillType()); + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/ApyAccount.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/ApyAccount.java new file mode 100644 index 0000000..950947d --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/ApyAccount.java @@ -0,0 +1,188 @@ + + +package org.wfc.payment.unionpay.model; + + +/** + * 支付账户 + * + */ +//@Table(name = "apy_account") +//@Entity +public class ApyAccount { + // 支付账号id +// @Id +// @GeneratedValue +// @Column(name = "pay_id") + private Integer payId; + // 支付合作id,商户id,差不多是支付平台的账号或id +// @Column(name = "partner") + private String partner; + // 应用id +// @Column(name = "appid") + private String appId; + // 支付平台公钥(签名校验使用),sign_type只有单一key时public_key与private_key相等,比如sign_type=MD5的情况 + private String publicKey; + // 应用私钥(生成签名) +// @Column(name = "private_key") + private String privateKey; + // 异步回调地址 +// @Column(name = "notify_url") + private String notifyUrl; + // 同步回调地址 +// @Column(name = "return_url") + private String returnUrl; + // 收款账号 +// @Column(name = "seller") + private String seller; + + //请求证书地址,请使用绝对路径 +// @Column(name = "keystore_path") + private String keystorePath; + //证书对应的密码 +// @Column(name = "store_password") + private String storePassword; + + + // 签名类型 +// @Column(name = "sign_type") + private String signType; + // 编码类型 枚举值,字符编码 utf-8,gbk等等 +// @Column(name = "input_charset") + private String inputCharset; + //支付类型,aliPay:支付宝,wxPay:微信, youdianPay: 友店微信,此处开发者自定义对应com.egzosn.pay.demo.entity.PayType枚举值 +// @Enumerated(EnumType.STRING) +// @Column(name = "pay_type") + private PayType payType; + //是否为测试环境 + private boolean isTest = false; + + public Integer getPayId() { + return payId; + } + + public void setPayId(Integer payId) { + this.payId = payId; + } + + public String getPartner() { + return partner; + } + + public void setPartner(String partner) { + this.partner = partner; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public String getNotifyUrl() { + return notifyUrl; + } + + public void setNotifyUrl(String notifyUrl) { + this.notifyUrl = notifyUrl; + } + + public String getReturnUrl() { + return returnUrl; + } + + public void setReturnUrl(String returnUrl) { + this.returnUrl = returnUrl; + } + + public String getSeller() { + return seller; + } + + public void setSeller(String seller) { + this.seller = seller; + } + + public String getSignType() { + return signType; + } + + public void setSignType(String signType) { + this.signType = signType; + } + + public PayType getPayType() { + return payType; + } + + public void setPayType(PayType payType) { + this.payType = payType; + } + + public String getInputCharset() { + return inputCharset; + } + + public void setInputCharset(String inputCharset) { + this.inputCharset = inputCharset; + } + + public boolean isTest() { + return isTest; + } + + public String getKeystorePath() { + return keystorePath; + } + + public void setKeystorePath(String keystorePath) { + this.keystorePath = keystorePath; + } + + public String getStorePassword() { + return storePassword; + } + + public void setStorePassword(String storePassword) { + this.storePassword = storePassword; + } + + public void setTest(boolean test) { + isTest = test; + } + + @Override + public String toString() { + return "ApyAccount{" + + "payId=" + payId + + ", partner='" + partner + '\'' + + ", appId='" + appId + '\'' + + ", publicKey='" + publicKey + '\'' + + ", privateKey='" + privateKey + '\'' + + ", notifyUrl='" + notifyUrl + '\'' + + ", returnUrl='" + returnUrl + '\'' + + ", seller='" + seller + '\'' + + ", signType='" + signType + '\'' + + ", inputCharset='" + inputCharset + '\'' + + ", payType=" + payType + + '}'; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/PayType.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/PayType.java new file mode 100644 index 0000000..997e95c --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/PayType.java @@ -0,0 +1,56 @@ +package org.wfc.payment.unionpay.model; + +import org.wfc.payment.unionpay.service.impl.UnionPayMessageImpl; + +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.BasePayType; +import com.egzosn.pay.common.bean.CertStoreType; +import com.egzosn.pay.common.bean.TransactionType; +import com.egzosn.pay.union.api.UnionPayConfigStorage; +import com.egzosn.pay.union.api.UnionPayService; +import com.egzosn.pay.union.bean.UnionTransactionType; + +/** + * 支付类型 + * + */ +public enum PayType implements BasePayType { + UNION_PAY { + @Override + public PayService getPayService(ApyAccount apyAccount) { + UnionPayConfigStorage unionPayConfigStorage = new UnionPayConfigStorage(); + unionPayConfigStorage.setMerId(apyAccount.getPartner()); + unionPayConfigStorage.setCertSign(true); + + //中级证书路径 + unionPayConfigStorage.setAcpMiddleCert("D:/certs/acp_test_middle.cer"); + //根证书路径 + unionPayConfigStorage.setAcpRootCert("D:/certs/acp_test_root.cer"); + // 私钥证书路径 + unionPayConfigStorage.setKeyPrivateCert("D:/certs/acp_test_sign.pfx"); + //私钥证书对应的密码 + unionPayConfigStorage.setKeyPrivateCertPwd("000000"); + //设置证书对应的存储方式,这里默认为文件地址 + unionPayConfigStorage.setCertStoreType(CertStoreType.PATH); + + unionPayConfigStorage.setNotifyUrl(apyAccount.getNotifyUrl()); + unionPayConfigStorage.setReturnUrl(apyAccount.getReturnUrl()); + unionPayConfigStorage.setSignType(apyAccount.getSignType()); + unionPayConfigStorage.setPayType(apyAccount.getPayType().toString()); + unionPayConfigStorage.setInputCharset(apyAccount.getInputCharset()); + unionPayConfigStorage.setTest(apyAccount.isTest()); + final UnionPayService unionPayService = new UnionPayService(unionPayConfigStorage); + unionPayService.setPayMessageHandler(new UnionPayMessageImpl(apyAccount.getPayId())); + return unionPayService; + } + + @Override + public TransactionType getTransactionType(String transactionType) { + return UnionTransactionType.valueOf(transactionType); + } + + + }; + + public abstract PayService getPayService(ApyAccount apyAccount); +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/QueryOrder.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/QueryOrder.java new file mode 100644 index 0000000..8720bb1 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/model/QueryOrder.java @@ -0,0 +1,130 @@ +package org.wfc.payment.unionpay.model; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单辅助接口 + * @author egan + * email egzosn@gmail.com + * date 2017/3/12 14:50 + */ +public class QueryOrder { + + private Integer payId; +// 支付平台订单号 + private String tradeNo; + +// 商户单号 + private String outTradeNo; +// 退款金额 + private BigDecimal refundAmount; +// 总金额 + private BigDecimal totalAmount; +// 账单时间:具体请查看对应支付平台 + private Date billDate; +// 账单时间:具体请查看对应支付平台 + private String billType; + // 支付平台订单号或者账单日期 + private Object tradeNoOrBillDate; + // 商户单号或者 账单类型 + private String outTradeNoBillType; + // 交易类型 + private String transactionType; + + public Integer getPayId() { + return payId; + } + + public void setPayId(Integer payId) { + this.payId = payId; + } + + public String getTradeNo() { + return tradeNo; + } + + public void setTradeNo(String tradeNo) { + this.tradeNo = tradeNo; + } + + public String getOutTradeNo() { + return outTradeNo; + } + + public void setOutTradeNo(String outTradeNo) { + this.outTradeNo = outTradeNo; + } + + public BigDecimal getRefundAmount() { + return refundAmount; + } + + public void setRefundAmount(BigDecimal refundAmount) { + this.refundAmount = refundAmount; + } + + public BigDecimal getTotalAmount() { + return totalAmount; + } + + public void setTotalAmount(BigDecimal totalAmount) { + this.totalAmount = totalAmount; + } + + public Date getBillDate() { + return billDate; + } + + public void setBillDate(Date billDate) { + this.billDate = billDate; + } + + public String getBillType() { + return billType; + } + + public void setBillType(String billType) { + this.billType = billType; + } + + public Object getTradeNoOrBillDate() { + return tradeNoOrBillDate; + } + + public void setTradeNoOrBillDate(Object tradeNoOrBillDate) { + this.tradeNoOrBillDate = tradeNoOrBillDate; + } + + public String getOutTradeNoBillType() { + return outTradeNoBillType; + } + + public void setOutTradeNoBillType(String outTradeNoBillType) { + this.outTradeNoBillType = outTradeNoBillType; + } + + public String getTransactionType() { + return transactionType; + } + + public void setTransactionType(String transactionType) { + this.transactionType = transactionType; + } + + @Override + public String toString() { + return "QueryOrder{" + + "payId=" + payId + + ", tradeNo='" + tradeNo + '\'' + + ", outTradeNo='" + outTradeNo + '\'' + + ", refundAmount=" + refundAmount + + ", totalAmount=" + totalAmount + + ", billDate=" + billDate + + ", billType='" + billType + '\'' + + ", tradeNoOrBillDate=" + tradeNoOrBillDate + + ", outTradeNoBillType='" + outTradeNoBillType + '\'' + + ", transactionType='" + transactionType + '\'' + + '}'; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/repository/ApyAccountRepository.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/repository/ApyAccountRepository.java new file mode 100644 index 0000000..8111010 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/repository/ApyAccountRepository.java @@ -0,0 +1,69 @@ + +package org.wfc.payment.unionpay.repository; + +import java.util.HashMap; +import java.util.Map; + +import org.wfc.payment.unionpay.model.ApyAccount; +import org.wfc.payment.unionpay.model.PayType; + +import com.egzosn.pay.common.util.sign.SignUtils; + +/** + * 账户 + * + * @author egan + * email egzosn@gmail.com + * date 2016/11/18 1:21 + */ +//@Repository +public class ApyAccountRepository { + + // 这里简单模拟,引入orm等框架之后可自行删除 + protected static final Map apyAccounts = new HashMap<>(); + + /** + * 这里简单初始化,引入orm等框架之后可自行删除 + */ + static { + + ApyAccount apyAccount4 = new ApyAccount(); + apyAccount4.setPayId(4); + apyAccount4.setPartner("700000000000001"); + //公钥,验签证书链格式: 中级证书路径;根证书路径 + apyAccount4.setPublicKey("D:/certs/acp_test_middle.cer;D:/certs/acp_test_root.cer"); + //私钥, 私钥证书格式: 私钥证书路径;私钥证书对应的密码 + apyAccount4.setPrivateKey("D:/certs/acp_test_sign.pfx;000000"); + apyAccount4.setNotifyUrl("http://127.0.0.1/payBack4.json"); + // 无需同步回调可不填 app填这个就可以 + apyAccount4.setReturnUrl("http://127.0.0.1/payBack4.json"); + apyAccount4.setSeller(""); + apyAccount4.setInputCharset("UTF-8"); + apyAccount4.setSignType(SignUtils.RSA2.name()); + apyAccount4.setPayType(PayType.UNION_PAY); + apyAccount4.setTest(true); + apyAccounts.put(apyAccount4.getPayId(), apyAccount4); + } + //_____________________________________________________________ + + /** + * Provides access to the apyAccounts map. + * + * @return the apyAccounts map + */ + public static Map getApyAccounts() { + return apyAccounts; + } + + + /** + * 根据id获取对应的账户信息 + * + * @param payId 账户id + * @return 账户信息 + */ + public ApyAccount findByPayId(Integer payId) { + // Retrieve the ApyAccount from the map using the payId + return apyAccounts.get(payId); + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/ApyAccountService.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/ApyAccountService.java new file mode 100644 index 0000000..51d9780 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/ApyAccountService.java @@ -0,0 +1,55 @@ + +package org.wfc.payment.unionpay.service; + +import org.wfc.payment.unionpay.model.ApyAccount; +import org.wfc.payment.unionpay.repository.ApyAccountRepository; +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.springframework.stereotype.Service; +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +@Service +public class ApyAccountService { + +// @Resource + private ApyAccountRepository dao; + + @Resource + private AutowireCapableBeanFactory spring; + + /** + * 缓存 + */ + private static final Map payResponses = new HashMap<>(); + + /** + * 这里简单初始化,引入orm等框架之后可自行删除 + */ + public ApyAccountService() { + dao = new ApyAccountRepository(); + } + + + /** + * 获取支付响应 + * @param id 账户id + * @return 支付响应 + */ + public PayResponse getPayResponse(Integer id) { + + PayResponse payResponse = payResponses.get(id); + if (payResponse == null) { + ApyAccount apyAccount = dao.findByPayId(id); + if (apyAccount == null) { + throw new IllegalArgumentException ("无法查询"); + } + payResponse = new PayResponse(); + spring.autowireBean(payResponse); + payResponse.init(apyAccount); + payResponses.put(id, payResponse); + // 查询 + } + return payResponse; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/PayResponse.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/PayResponse.java new file mode 100644 index 0000000..37ed40c --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/PayResponse.java @@ -0,0 +1,79 @@ +package org.wfc.payment.unionpay.service; + +import javax.annotation.Resource; + +import org.springframework.beans.factory.config.AutowireCapableBeanFactory; +import org.wfc.payment.unionpay.model.ApyAccount; + +import com.egzosn.pay.common.api.PayConfigStorage; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.http.HttpConfigStorage; + +/** + * 支付响应对象 + * + */ +public class PayResponse { + + @Resource + private AutowireCapableBeanFactory spring; + + private PayConfigStorage storage; + + private PayService service; + + + public PayResponse() { + // This constructor is intentionally empty. Nothing special is needed here. + } + + /** + * 初始化支付配置 + * + * @param apyAccount 账户信息 + * @see ApyAccount 对应表结构详情--》 /pay-java-demo/resources/apy_account.sql + */ + public void init(ApyAccount apyAccount) { + //根据不同的账户类型 初始化支付配置 + this.service = apyAccount.getPayType().getPayService(apyAccount); + this.storage = service.getPayConfigStorage(); + + + //这里设置http请求配置 + // buildRouter(apyAccount.getPayId()); // Deprecated method call removed + } + + /** + * 获取http配置,如果配置为null则为默认配置,无代理,无证书的请求方式。 + * 此处非必需 + * + * @param apyAccount 账户信息 + * @return 请求配置 + */ + public HttpConfigStorage getHttpConfigStorage(ApyAccount apyAccount) { + HttpConfigStorage httpConfigStorage = new HttpConfigStorage(); + /* 网路代理配置 根据需求进行设置*/ + // //http代理地址 + // httpConfigStorage.setHttpProxyHost("192.168.1.69"); + // //代理端口 + // httpConfigStorage.setHttpProxyPort(3308); + // //代理用户名 + // httpConfigStorage.setHttpProxyUsername("user"); + // //代理密码 + // httpConfigStorage.setHttpProxyPassword("password"); + // 设置ssl证书路径 https证书设置 方式二 + httpConfigStorage.setKeystore(apyAccount.getKeystorePath()); + //设置ssl证书对应的密码 + httpConfigStorage.setStorePassword(apyAccount.getStorePassword()); + return httpConfigStorage; + } + + public PayConfigStorage getStorage() { + return storage; + } + + public PayService getService() { + return service; + } + +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/BasePayMessageImpl.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/BasePayMessageImpl.java new file mode 100644 index 0000000..aa4b8d7 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/BasePayMessageImpl.java @@ -0,0 +1,18 @@ +package org.wfc.payment.unionpay.service.impl; + +import com.egzosn.pay.common.api.PayMessageHandler; +import com.egzosn.pay.common.api.PayService; +import com.egzosn.pay.common.bean.PayMessage; + +public abstract class BasePayMessageImpl> implements PayMessageHandler { + //支付账户id + private Integer payId; + + protected BasePayMessageImpl(Integer payId) { + this.payId = payId; + } + + public Integer getPayId() { + return payId; + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/UnionPayMessageImpl.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/UnionPayMessageImpl.java new file mode 100644 index 0000000..8a38123 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/unionpay/service/impl/UnionPayMessageImpl.java @@ -0,0 +1,28 @@ +package org.wfc.payment.unionpay.service.impl; + +import com.egzosn.pay.common.bean.PayOutMessage; +import com.egzosn.pay.common.exception.PayErrorException; +import com.egzosn.pay.union.api.UnionPayService; +import com.egzosn.pay.union.bean.SDKConstants; +import com.egzosn.pay.union.bean.UnionPayMessage; + +import java.util.Map; + +public class UnionPayMessageImpl extends BasePayMessageImpl { + + public UnionPayMessageImpl(Integer payId) { + super(payId); + } + + @Override + public PayOutMessage handle(UnionPayMessage payMessage, Map context, UnionPayService payService) throws PayErrorException { + //交易状态 + if (SDKConstants.OK_RESP_CODE.equals(payMessage.getPayMessage().get(SDKConstants.param_respCode))) { + /////这里进行成功的处理 + + return payService.successPayOutMessage(payMessage); + } + + return payService.getPayOutMessage("fail", "失败"); + } +} diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/config/SwaggerConfig.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/config/SwaggerConfig.java index 72bd6d8..4cbff23 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/config/SwaggerConfig.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/config/SwaggerConfig.java @@ -29,7 +29,7 @@ public class SwaggerConfig extends WebMvcConfigurationSupport { public GroupedOpenApi publicApi() { return GroupedOpenApi.builder() .group("public") - .pathsToMatch("/api/payment/**") + .pathsToMatch("/payment/**") .packagesToScan("org.wfc.payment.wxpay.controller") .build(); } diff --git a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/controller/WxPayController.java b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/controller/WxPayController.java index 2955441..fb5f655 100644 --- a/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/controller/WxPayController.java +++ b/wfc-modules/wfc-payment/src/main/java/org/wfc/payment/wxpay/controller/WxPayController.java @@ -19,368 +19,381 @@ import org.springframework.web.bind.annotation.*; import java.io.File; import java.util.Date; - @Tag(name = "WeChat Pay") @RestController @RequestMapping("/wxpay") -@AllArgsConstructor +// @AllArgsConstructor public class WxPayController { - @Autowired - private WxPayService wxService; - /** - *

-   * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
-   * 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
-   * 需要调用查询接口的情况:
-   * ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
-   * ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
-   * ◆ 调用被扫支付API,返回USERPAYING的状态;
-   * ◆ 调用关单或撤销接口API之前,需确认支付状态;
-   * 接口地址:https://api.mch.weixin.qq.com/pay/orderquery
-   * 
- * - * @param transactionId 微信订单号 - * @param outTradeNo 商户系统内部的订单号,当没提供transactionId时需要传这个。 - */ - @Operation(summary = "Query order") - @GetMapping("/queryOrder") - public WxPayOrderQueryResult queryOrder(@RequestParam(required = false) String transactionId, - @RequestParam(required = false) String outTradeNo) - throws WxPayException { - return this.wxService.queryOrder(transactionId, outTradeNo); - } + private final WxPayService wxService; - @Operation(summary = "Query order") - @PostMapping("/queryOrder") - public WxPayOrderQueryResult queryOrder(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) throws WxPayException { - return this.wxService.queryOrder(wxPayOrderQueryRequest); - } + @Autowired + public WxPayController(WxPayService wxService) { + this.wxService = wxService; + } - /** - *
-   * 关闭订单
-   * 应用场景
-   * 以下情况需要调用关单接口:
-   * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
-   * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
-   * 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
-   * 接口地址:https://api.mch.weixin.qq.com/pay/closeorder
-   * 是否需要证书:   不需要。
-   * 
- * - * @param outTradeNo 商户系统内部的订单号 - */ - @Operation(summary = "Close order") - @GetMapping("/closeOrder/{outTradeNo}") - public WxPayOrderCloseResult closeOrder(@PathVariable String outTradeNo) throws WxPayException { - return this.wxService.closeOrder(outTradeNo); - } + /** + *
+     * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2)
+     * 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
+     * 需要调用查询接口的情况:
+     * ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
+     * ◆ 调用支付接口后,返回系统错误或未知交易状态情况;
+     * ◆ 调用被扫支付API,返回USERPAYING的状态;
+     * ◆ 调用关单或撤销接口API之前,需确认支付状态;
+     * 接口地址:https://api.mch.weixin.qq.com/pay/orderquery
+     * 
+ * + * @param transactionId 微信订单号 + * @param outTradeNo 商户系统内部的订单号,当没提供transactionId时需要传这个。 + */ + @Operation(summary = "Query order") + @GetMapping("/queryOrder") + public WxPayOrderQueryResult queryOrder(@RequestParam(required = false) String transactionId, + @RequestParam(required = false) String outTradeNo) + throws WxPayException { + return this.wxService.queryOrder(transactionId, outTradeNo); + } - @Operation(summary = "Close order") - @PostMapping("/closeOrder") - public WxPayOrderCloseResult closeOrder(@RequestBody WxPayOrderCloseRequest wxPayOrderCloseRequest) throws WxPayException { - return this.wxService.closeOrder(wxPayOrderCloseRequest); - } + @Operation(summary = "Query order") + @PostMapping("/queryOrder") + public WxPayOrderQueryResult queryOrder(@RequestBody WxPayOrderQueryRequest wxPayOrderQueryRequest) + throws WxPayException { + return this.wxService.queryOrder(wxPayOrderQueryRequest); + } - /** - * 调用统一下单接口,并组装生成支付所需参数对象. - * - * @param request 统一下单请求参数 - * @param 请使用{@link com.github.binarywang.wxpay.bean.order}包下的类 - * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象 - */ - @Operation(summary = "统一下单,并组装所需支付参数") - @PostMapping("/createOrder") - public T createOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException { - return this.wxService.createOrder(request); - } + /** + *
+     * 关闭订单
+     * 应用场景
+     * 以下情况需要调用关单接口:
+     * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付;
+     * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。
+     * 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。
+     * 接口地址:https://api.mch.weixin.qq.com/pay/closeorder
+     * 是否需要证书:   不需要。
+     * 
+ * + * @param outTradeNo 商户系统内部的订单号 + */ + @Operation(summary = "Close order") + @GetMapping("/closeOrder/{outTradeNo}") + public WxPayOrderCloseResult closeOrder(@PathVariable String outTradeNo) throws WxPayException { + return this.wxService.closeOrder(outTradeNo); + } - /** - * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) - * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" - * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder - * - * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) - */ - @Operation(summary = "原生的统一下单接口") - @PostMapping("/unifiedOrder") - public WxPayUnifiedOrderResult unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException { - return this.wxService.unifiedOrder(request); - } + @Operation(summary = "Close order") + @PostMapping("/closeOrder") + public WxPayOrderCloseResult closeOrder(@RequestBody WxPayOrderCloseRequest wxPayOrderCloseRequest) + throws WxPayException { + return this.wxService.closeOrder(wxPayOrderCloseRequest); + } - /** - *
-   * 微信支付-申请退款
-   * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
-   * 接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund
-   * 
- * - * @param request 请求对象 - * @return 退款操作结果 - */ - @Operation(summary = "Refund") - @PostMapping("/refund") - public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException { - return this.wxService.refund(request); - } + /** + * 调用统一下单接口,并组装生成支付所需参数对象. + * + * @param request 统一下单请求参数 + * @param 请使用{@link com.github.binarywang.wxpay.bean.order}包下的类 + * @return 返回 {@link com.github.binarywang.wxpay.bean.order}包下的类对象 + */ + @Operation(summary = "统一下单,并组装所需支付参数") + @PostMapping("/createOrder") + public T createOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException { + return this.wxService.createOrder(request); + } - /** - *
-   * 微信支付-查询退款
-   * 应用场景:
-   *  提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,
-   *  银行卡支付的退款3个工作日后重新查询退款状态。
-   * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
-   * 接口链接:https://api.mch.weixin.qq.com/pay/refundquery
-   * 
- * 以下四个参数四选一 - * - * @param transactionId 微信订单号 - * @param outTradeNo 商户订单号 - * @param outRefundNo 商户退款单号 - * @param refundId 微信退款单号 - * @return 退款信息 - */ - @Operation(summary = "退款查询") - @GetMapping("/refundQuery") - public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId, - @RequestParam(required = false) String outTradeNo, - @RequestParam(required = false) String outRefundNo, - @RequestParam(required = false) String refundId) - throws WxPayException { - return this.wxService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId); - } + /** + * 统一下单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) + * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" + * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder + * + * @param request 请求对象,注意一些参数如appid、mchid等不用设置,方法内会自动从配置对象中获取到(前提是对应配置中已经设置) + */ + @Operation(summary = "原生的统一下单接口") + @PostMapping("/unifiedOrder") + public WxPayUnifiedOrderResult unifiedOrder(@RequestBody WxPayUnifiedOrderRequest request) throws WxPayException { + return this.wxService.unifiedOrder(request); + } - @Operation(summary = "退款查询") - @PostMapping("/refundQuery") - public WxPayRefundQueryResult refundQuery(@RequestBody WxPayRefundQueryRequest wxPayRefundQueryRequest) throws WxPayException { - return this.wxService.refundQuery(wxPayRefundQueryRequest); - } + /** + *
+     * 微信支付-申请退款
+     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_4
+     * 接口链接:https://api.mch.weixin.qq.com/secapi/pay/refund
+     * 
+ * + * @param request 请求对象 + * @return 退款操作结果 + */ + @Operation(summary = "Refund") + @PostMapping("/refund") + public WxPayRefundResult refund(@RequestBody WxPayRefundRequest request) throws WxPayException { + return this.wxService.refund(request); + } - @Operation(summary = "支付回调通知处理") - @PostMapping("/notify/order") - public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException { - final WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData); - // TODO 根据自己业务场景需要构造返回对象 - return WxPayNotifyResponse.success("成功"); - } + /** + *
+     * 微信支付-查询退款
+     * 应用场景:
+     *  提交退款申请后,通过调用该接口查询退款状态。退款有一定延时,用零钱支付的退款20分钟内到账,
+     *  银行卡支付的退款3个工作日后重新查询退款状态。
+     * 详见 https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_5
+     * 接口链接:https://api.mch.weixin.qq.com/pay/refundquery
+     * 
+ * + * 以下四个参数四选一 + * + * @param transactionId 微信订单号 + * @param outTradeNo 商户订单号 + * @param outRefundNo 商户退款单号 + * @param refundId 微信退款单号 + * @return 退款信息 + */ + @Operation(summary = "退款查询") + @GetMapping("/refundQuery") + public WxPayRefundQueryResult refundQuery(@RequestParam(required = false) String transactionId, + @RequestParam(required = false) String outTradeNo, + @RequestParam(required = false) String outRefundNo, + @RequestParam(required = false) String refundId) + throws WxPayException { + return this.wxService.refundQuery(transactionId, outTradeNo, outRefundNo, refundId); + } - @Operation(summary = "退款回调通知处理") - @PostMapping("/notify/refund") - public String parseRefundNotifyResult(@RequestBody String xmlData) throws WxPayException { - final WxPayRefundNotifyResult result = this.wxService.parseRefundNotifyResult(xmlData); - // TODO 根据自己业务场景需要构造返回对象 - return WxPayNotifyResponse.success("成功"); - } + @Operation(summary = "退款查询") + @PostMapping("/refundQuery") + public WxPayRefundQueryResult refundQuery(@RequestBody WxPayRefundQueryRequest wxPayRefundQueryRequest) + throws WxPayException { + return this.wxService.refundQuery(wxPayRefundQueryRequest); + } - @Operation(summary = "扫码支付回调通知处理") - @PostMapping("/notify/scanpay") - public String parseScanPayNotifyResult(String xmlData) throws WxPayException { - final WxScanPayNotifyResult result = this.wxService.parseScanPayNotifyResult(xmlData); - // TODO 根据自己业务场景需要构造返回对象 - return WxPayNotifyResponse.success("成功"); - } + @Operation(summary = "支付回调通知处理") + @PostMapping("/notify/order") + public String parseOrderNotifyResult(@RequestBody String xmlData) throws WxPayException { + final WxPayOrderNotifyResult notifyResult = this.wxService.parseOrderNotifyResult(xmlData); + // TODO 根据自己业务场景需要构造返回对象 + return WxPayNotifyResponse.success("成功"); + } - /** - * 发送微信红包给个人用户 - *
-   * 文档详见:
-   * 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
-   *  接口地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
-   * 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
-   *  接口地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack
-   * 
- * - * @param request 请求对象 - */ - @Operation(summary = "发送红包") - @PostMapping("/sendRedpack") - public WxPaySendRedpackResult sendRedpack(@RequestBody WxPaySendRedpackRequest request) throws WxPayException { - return this.wxService.getRedpackService().sendRedpack(request); - } + @Operation(summary = "退款回调通知处理") + @PostMapping("/notify/refund") + public String parseRefundNotifyResult(@RequestBody String xmlData) throws WxPayException { + final WxPayRefundNotifyResult result = this.wxService.parseRefundNotifyResult(xmlData); + // TODO 根据自己业务场景需要构造返回对象 + return WxPayNotifyResponse.success("成功"); + } - /** - *
-   *   查询红包记录
-   *   用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包。
-   *   请求Url	https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo
-   *   是否需要证书	是(证书及使用说明详见商户证书)
-   *   请求方式	POST
-   * 
- * - * @param mchBillNo 商户发放红包的商户订单号,比如10000098201411111234567890 - */ - @Operation(summary = "查询红包") - @GetMapping("/queryRedpack/{mchBillNo}") - public WxPayRedpackQueryResult queryRedpack(@PathVariable String mchBillNo) throws WxPayException { - return this.wxService.getRedpackService().queryRedpack(mchBillNo); - } + @Operation(summary = "扫码支付回调通知处理") + @PostMapping("/notify/scanpay") + public String parseScanPayNotifyResult(String xmlData) throws WxPayException { + final WxScanPayNotifyResult result = this.wxService.parseScanPayNotifyResult(xmlData); + // TODO 根据自己业务场景需要构造返回对象 + return WxPayNotifyResponse.success("成功"); + } - /** - *
-   * 扫码支付模式一生成二维码的方法
-   * 二维码中的内容为链接,形式为:
-   * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
-   * 其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片。
-   * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
-   * 
- * - * @param productId 产品Id - * @param logoFile 商户logo图片的文件对象,可以为空 - * @param sideLength 要生成的二维码的边长,如果为空,则取默认值400 - * @return 生成的二维码的字节数组 - */ - public byte[] createScanPayQrcodeMode1(String productId, File logoFile, Integer sideLength) throws Exception { - return this.wxService.createScanPayQrcodeMode1(productId, logoFile, sideLength); - } + /** + * 发送微信红包给个人用户 + * + *
+     * 文档详见:
+     * 发送普通红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_4&index=3
+     *  接口地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendredpack
+     * 发送裂变红包 https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=13_5&index=4
+     *  接口地址:https://api.mch.weixin.qq.com/mmpaymkttransfers/sendgroupredpack
+     * 
+ * + * @param request 请求对象 + */ + @Operation(summary = "发送红包") + @PostMapping("/sendRedpack") + public WxPaySendRedpackResult sendRedpack(@RequestBody WxPaySendRedpackRequest request) throws WxPayException { + return this.wxService.getRedpackService().sendRedpack(request); + } - /** - *
-   * 扫码支付模式一生成二维码的方法
-   * 二维码中的内容为链接,形式为:
-   * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
-   * 其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片。
-   * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
-   * 
- * - * @param productId 产品Id - * @return 生成的二维码URL连接 - */ - public String createScanPayQrcodeMode1(String productId) { - return this.wxService.createScanPayQrcodeMode1(productId); - } + /** + *
+     *   查询红包记录
+     *   用于商户对已发放的红包进行查询红包的具体信息,可支持普通红包和裂变包。
+     *   请求Url	https://api.mch.weixin.qq.com/mmpaymkttransfers/gethbinfo
+     *   是否需要证书	是(证书及使用说明详见商户证书)
+     *   请求方式	POST
+     * 
+ * + * @param mchBillNo 商户发放红包的商户订单号,比如10000098201411111234567890 + */ + @Operation(summary = "查询红包") + @GetMapping("/queryRedpack/{mchBillNo}") + public WxPayRedpackQueryResult queryRedpack(@PathVariable String mchBillNo) throws WxPayException { + return this.wxService.getRedpackService().queryRedpack(mchBillNo); + } - /** - *
-   * 扫码支付模式二生成二维码的方法
-   * 对应链接格式:weixin://wxpay/bizpayurl?sr=XXXXX。请商户调用第三方库将code_url生成二维码图片。
-   * 该模式链接较短,生成的二维码打印到结账小票上的识别率较高。
-   * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
-   * 
- * - * @param codeUrl 微信返回的交易会话的二维码链接 - * @param logoFile 商户logo图片的文件对象,可以为空 - * @param sideLength 要生成的二维码的边长,如果为空,则取默认值400 - * @return 生成的二维码的字节数组 - */ - public byte[] createScanPayQrcodeMode2(String codeUrl, File logoFile, Integer sideLength) throws Exception { - return this.wxService.createScanPayQrcodeMode2(codeUrl, logoFile, sideLength); - } + /** + *
+     * 扫码支付模式一生成二维码的方法
+     * 二维码中的内容为链接,形式为:
+     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
+     * 其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片。
+     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
+     * 
+ * + * @param productId 产品Id + * @param logoFile 商户logo图片的文件对象,可以为空 + * @param sideLength 要生成的二维码的边长,如果为空,则取默认值400 + * @return 生成的二维码的字节数组 + */ + public byte[] createScanPayQrcodeMode1(String productId, File logoFile, Integer sideLength) throws Exception { + return this.wxService.createScanPayQrcodeMode1(productId, logoFile, sideLength); + } - /** - *
-   * 交易保障
-   * 应用场景:
-   *  商户在调用微信支付提供的相关接口时,会得到微信支付返回的相关信息以及获得整个接口的响应时间。
-   *  为提高整体的服务水平,协助商户一起提高服务质量,微信支付提供了相关接口调用耗时和返回信息的主动上报接口,
-   *  微信支付可以根据商户侧上报的数据进一步优化网络部署,完善服务监控,和商户更好的协作为用户提供更好的业务体验。
-   * 接口地址: https://api.mch.weixin.qq.com/payitil/report
-   * 是否需要证书:不需要
-   * 
- */ - @Operation(summary = "提交交易保障数据") - @PostMapping("/report") - public void report(@RequestBody WxPayReportRequest request) throws WxPayException { - this.wxService.report(request); - } + /** + *
+     * 扫码支付模式一生成二维码的方法
+     * 二维码中的内容为链接,形式为:
+     * weixin://wxpay/bizpayurl?sign=XXXXX&appid=XXXXX&mch_id=XXXXX&product_id=XXXXXX&time_stamp=XXXXXX&nonce_str=XXXXX
+     * 其中XXXXX为商户需要填写的内容,商户将该链接生成二维码,如需要打印发布二维码,需要采用此格式。商户可调用第三方库生成二维码图片。
+     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
+     * 
+ * + * @param productId 产品Id + * @return 生成的二维码URL连接 + */ + public String createScanPayQrcodeMode1(String productId) { + return this.wxService.createScanPayQrcodeMode1(productId); + } - /** - *
-   * 下载对账单
-   * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致,通过对账单核对后可校正支付状态。
-   * 注意:
-   * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致,bill_type为REVOKED;
-   * 2、微信在次日9点启动生成前一天的对账单,建议商户10点后再获取;
-   * 3、对账单中涉及金额的字段单位为“元”。
-   * 4、对账单接口只能下载三个月以内的账单。
-   * 接口链接:https://api.mch.weixin.qq.com/pay/downloadbill
-   * 详情请见: 下载对账单
-   * 
- * - * @param billDate 对账单日期 bill_date 下载对账单的日期,格式:20140603 - * @param billType 账单类型 bill_type ALL,返回当日所有订单信息,默认值,SUCCESS,返回当日成功支付的订单,REFUND,返回当日退款订单 - * @param tarType 压缩账单 tar_type 非必传参数,固定值:GZIP,返回格式为.gzip的压缩包账单。不传则默认为数据流形式。 - * @param deviceInfo 设备号 device_info 非必传参数,终端设备号 - * @return 保存到本地的临时文件 - */ - @Operation(summary = "下载对账单") - @GetMapping("/downloadBill/{billDate}/{billType}/{tarType}/{deviceInfo}") - public WxPayBillResult downloadBill(@PathVariable String billDate, @PathVariable String billType, - @PathVariable String tarType, @PathVariable String deviceInfo) throws WxPayException { - return this.wxService.downloadBill(billDate, billType, tarType, deviceInfo); - } + /** + *
+     * 扫码支付模式二生成二维码的方法
+     * 对应链接格式:weixin://wxpay/bizpayurl?sr=XXXXX。请商户调用第三方库将code_url生成二维码图片。
+     * 该模式链接较短,生成的二维码打印到结账小票上的识别率较高。
+     * 文档详见: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_5
+     * 
+ * + * @param codeUrl 微信返回的交易会话的二维码链接 + * @param logoFile 商户logo图片的文件对象,可以为空 + * @param sideLength 要生成的二维码的边长,如果为空,则取默认值400 + * @return 生成的二维码的字节数组 + */ + public byte[] createScanPayQrcodeMode2(String codeUrl, File logoFile, Integer sideLength) throws Exception { + return this.wxService.createScanPayQrcodeMode2(codeUrl, logoFile, sideLength); + } - @Operation(summary = "下载对账单") - @PostMapping("/downloadBill") - public WxPayBillResult downloadBill(WxPayDownloadBillRequest wxPayDownloadBillRequest) throws WxPayException { - return this.wxService.downloadBill(wxPayDownloadBillRequest); - } + /** + *
+     * 交易保障
+     * 应用场景:
+     *  商户在调用微信支付提供的相关接口时,会得到微信支付返回的相关信息以及获得整个接口的响应时间。
+     *  为提高整体的服务水平,协助商户一起提高服务质量,微信支付提供了相关接口调用耗时和返回信息的主动上报接口,
+     *  微信支付可以根据商户侧上报的数据进一步优化网络部署,完善服务监控,和商户更好的协作为用户提供更好的业务体验。
+     * 接口地址: https://api.mch.weixin.qq.com/payitil/report
+     * 是否需要证书:不需要
+     * 
+ */ + @Operation(summary = "提交交易保障数据") + @PostMapping("/report") + public void report(@RequestBody WxPayReportRequest request) throws WxPayException { + this.wxService.report(request); + } - /** - *
-   * 提交刷卡支付
-   * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
-   * 应用场景:
-   * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,由商户收银台或者商户后台调用该接口发起支付。
-   * 提醒1:提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时,商户系统等待5秒后调用【查询订单API】,查询支付实际交易结果;当返回结果为“USERPAYING”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议30秒);
-   * 提醒2:在调用查询接口返回后,如果交易状况不明晰,请调用【撤销订单API】,此时如果交易失败则关闭订单,该单不能再支付成功;如果交易成功,则将扣款退回到用户账户。当撤销无返回或错误时,请再次调用。注意:请勿扣款后立即调用【撤销订单API】,建议至少15秒后再调用。撤销订单API需要双向证书。
-   * 接口地址:   https://api.mch.weixin.qq.com/pay/micropay
-   * 是否需要证书:不需要。
-   * 
- */ - @Operation(summary = "提交刷卡支付") - @PostMapping("/micropay") - public WxPayMicropayResult micropay(@RequestBody WxPayMicropayRequest request) throws WxPayException { - return this.wxService.micropay(request); - } + /** + *
+     * 下载对账单
+     * 商户可以通过该接口下载历史交易清单。比如掉单、系统错误等导致商户侧和微信侧数据不一致,通过对账单核对后可校正支付状态。
+     * 注意:
+     * 1、微信侧未成功下单的交易不会出现在对账单中。支付成功后撤销的交易会出现在对账单中,跟原支付单订单号一致,bill_type为REVOKED;
+     * 2、微信在次日9点启动生成前一天的对账单,建议商户10点后再获取;
+     * 3、对账单中涉及金额的字段单位为“元”。
+     * 4、对账单接口只能下载三个月以内的账单。
+     * 接口链接:https://api.mch.weixin.qq.com/pay/downloadbill
+     * 详情请见: 下载对账单
+     * 
+ * + * @param billDate 对账单日期 bill_date 下载对账单的日期,格式:20140603 + * @param billType 账单类型 bill_type + * ALL,返回当日所有订单信息,默认值,SUCCESS,返回当日成功支付的订单,REFUND,返回当日退款订单 + * @param tarType 压缩账单 tar_type 非必传参数,固定值:GZIP,返回格式为.gzip的压缩包账单。不传则默认为数据流形式。 + * @param deviceInfo 设备号 device_info 非必传参数,终端设备号 + * @return 保存到本地的临时文件 + */ + @Operation(summary = "下载对账单") + @GetMapping("/downloadBill/{billDate}/{billType}/{tarType}/{deviceInfo}") + public WxPayBillResult downloadBill(@PathVariable String billDate, @PathVariable String billType, + @PathVariable String tarType, @PathVariable String deviceInfo) throws WxPayException { + return this.wxService.downloadBill(billDate, billType, tarType, deviceInfo); + } - /** - *
-   * 撤销订单API
-   * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
-   * 应用场景:
-   *  支付交易返回失败或支付系统超时,调用该接口撤销交易。如果此订单用户支付失败,微信支付系统会将此订单关闭;如果用户支付成功,微信支付系统会将此订单资金退还给用户。
-   *  注意:7天以内的交易单可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款API。提交支付交易后调用【查询订单API】,没有明确的支付结果再调用【撤销订单API】。
-   *  调用支付接口后请勿立即调用撤销订单API,建议支付后至少15s后再调用撤销订单接口。
-   *  接口链接 :https://api.mch.weixin.qq.com/secapi/pay/reverse
-   *  是否需要证书:请求需要双向证书。
-   * 
- */ - @Operation(summary = "撤销订单") - @PostMapping("/reverseOrder") - public WxPayOrderReverseResult reverseOrder(@RequestBody WxPayOrderReverseRequest request) throws WxPayException { - return this.wxService.reverseOrder(request); - } + @Operation(summary = "下载对账单") + @PostMapping("/downloadBill") + public WxPayBillResult downloadBill(WxPayDownloadBillRequest wxPayDownloadBillRequest) throws WxPayException { + return this.wxService.downloadBill(wxPayDownloadBillRequest); + } - @Operation(summary = "获取沙箱环境签名key") - @GetMapping("/getSandboxSignKey") - public String getSandboxSignKey() throws WxPayException { - return this.wxService.getSandboxSignKey(); - } + /** + *
+     * 提交刷卡支付
+     * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_10&index=1
+     * 应用场景:
+     * 收银员使用扫码设备读取微信用户刷卡授权码以后,二维码或条码信息传送至商户收银台,由商户收银台或者商户后台调用该接口发起支付。
+     * 提醒1:提交支付请求后微信会同步返回支付结果。当返回结果为“系统错误”时,商户系统等待5秒后调用【查询订单API】,查询支付实际交易结果;当返回结果为“USERPAYING”时,商户系统可设置间隔时间(建议10秒)重新查询支付结果,直到支付成功或超时(建议30秒);
+     * 提醒2:在调用查询接口返回后,如果交易状况不明晰,请调用【撤销订单API】,此时如果交易失败则关闭订单,该单不能再支付成功;如果交易成功,则将扣款退回到用户账户。当撤销无返回或错误时,请再次调用。注意:请勿扣款后立即调用【撤销订单API】,建议至少15秒后再调用。撤销订单API需要双向证书。
+     * 接口地址:   https://api.mch.weixin.qq.com/pay/micropay
+     * 是否需要证书:不需要。
+     * 
+ */ + @Operation(summary = "提交刷卡支付") + @PostMapping("/micropay") + public WxPayMicropayResult micropay(@RequestBody WxPayMicropayRequest request) throws WxPayException { + return this.wxService.micropay(request); + } - @Operation(summary = "发放代金券") - @PostMapping("/sendCoupon") - public WxPayCouponSendResult sendCoupon(@RequestBody WxPayCouponSendRequest request) throws WxPayException { - return this.wxService.sendCoupon(request); - } + /** + *
+     * 撤销订单API
+     * 文档地址:https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=9_11&index=3
+     * 应用场景:
+     *  支付交易返回失败或支付系统超时,调用该接口撤销交易。如果此订单用户支付失败,微信支付系统会将此订单关闭;如果用户支付成功,微信支付系统会将此订单资金退还给用户。
+     *  注意:7天以内的交易单可调用撤销,其他正常支付的单如需实现相同功能请调用申请退款API。提交支付交易后调用【查询订单API】,没有明确的支付结果再调用【撤销订单API】。
+     *  调用支付接口后请勿立即调用撤销订单API,建议支付后至少15s后再调用撤销订单接口。
+     *  接口链接 :https://api.mch.weixin.qq.com/secapi/pay/reverse
+     *  是否需要证书:请求需要双向证书。
+     * 
+ */ + @Operation(summary = "撤销订单") + @PostMapping("/reverseOrder") + public WxPayOrderReverseResult reverseOrder(@RequestBody WxPayOrderReverseRequest request) throws WxPayException { + return this.wxService.reverseOrder(request); + } - @Operation(summary = "查询代金券批次") - @PostMapping("/queryCouponStock") - public WxPayCouponStockQueryResult queryCouponStock(@RequestBody WxPayCouponStockQueryRequest request) throws WxPayException { - return this.wxService.queryCouponStock(request); - } + @Operation(summary = "获取沙箱环境签名key") + @GetMapping("/getSandboxSignKey") + public String getSandboxSignKey() throws WxPayException { + return this.wxService.getSandboxSignKey(); + } - @Operation(summary = "查询代金券信息") - @PostMapping("/queryCouponInfo") - public WxPayCouponInfoQueryResult queryCouponInfo(@RequestBody WxPayCouponInfoQueryRequest request) throws WxPayException { - return this.wxService.queryCouponInfo(request); - } + @Operation(summary = "发放代金券") + @PostMapping("/sendCoupon") + public WxPayCouponSendResult sendCoupon(@RequestBody WxPayCouponSendRequest request) throws WxPayException { + return this.wxService.sendCoupon(request); + } - @Operation(summary = "拉取订单评价数据") - @PostMapping("/queryComment") - public String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit) throws WxPayException { - return this.wxService.queryComment(beginDate, endDate, offset, limit); - } + @Operation(summary = "查询代金券批次") + @PostMapping("/queryCouponStock") + public WxPayCouponStockQueryResult queryCouponStock(@RequestBody WxPayCouponStockQueryRequest request) + throws WxPayException { + return this.wxService.queryCouponStock(request); + } + + @Operation(summary = "查询代金券信息") + @PostMapping("/queryCouponInfo") + public WxPayCouponInfoQueryResult queryCouponInfo(@RequestBody WxPayCouponInfoQueryRequest request) + throws WxPayException { + return this.wxService.queryCouponInfo(request); + } + + @Operation(summary = "拉取订单评价数据") + @PostMapping("/queryComment") + public String queryComment(Date beginDate, Date endDate, Integer offset, Integer limit) throws WxPayException { + return this.wxService.queryComment(beginDate, endDate, offset, limit); + } } \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/application.yml b/wfc-modules/wfc-payment/src/main/resources/application.yml index ee72c43..4dee56b 100644 --- a/wfc-modules/wfc-payment/src/main/resources/application.yml +++ b/wfc-modules/wfc-payment/src/main/resources/application.yml @@ -55,7 +55,7 @@ spring: # mybatis-plus配置 mybatis-plus: # 搜索指定包别名 - type-aliases-package: org.wfc.system + type-aliases-package: org.wfc.payment.*.model # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapper-locations: classpath:mapper/**/*.xml global-config: @@ -72,19 +72,13 @@ swagger: logging: level: - root: debug - org: - springframework: - web: debug - mybatis: - mapper: debug - wfc: - system: debug - com: - github: - binarywang: debug - file: - name: /opt/wfc/logs/wfc-payment.log + root: info + org.springframework.web: info + org.mybatis.mapper: debug + com.github.binarywang: debug + org.wfc.payment.wxpay: debug + org.wfc.payment.alipay: debug + org.wfc.payment.ccpay: debug wxpay: appId: 121412414112 @@ -104,7 +98,7 @@ alipay: signType: RSA2 charset: utf-8 gatewayUrl: https://openapi.alipaydev.com/gateway.do - logPath: /opt/wfc/logs/alipay/alipay_log.txt + logPath: /opt/wfc/logs/alipay/alipay.log maxQueryRetry: 5 queryDuration: 5 maxCancelRetry: 3 @@ -117,9 +111,45 @@ alipay: supportPhone: ccpay: - url: https://api.paymentgateway.com/v1/payments + paymentUrl: https://api.paymentgateway.com/v1/payment + tokenUrl: https://api.paymentgateway.com/v1/token + queryUrl: https://api.paymentgateway.com/v1/query + refundUrl: https://api.paymentgateway.com/v1/refund apiKey: api-key merchantId: merchant-id currency: USD timeout: 30 - callbackUrl: https://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/payment/callback \ No newline at end of file + callbackUrl: https://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/payment/callback + +unionpay: + merId: 700000000000001 + keyPrivateCert: /opt/wfc/conf/cert/unionpay/wfc-union.pfx + keyPrivateCertPwd: 123456 + signCertType: PKCS12 + encryptCertPath: /opt/wfc/conf/cert/unionpay/wfc-union.cer + acpMiddleCert: /opt/wfc/conf/cert/unionpay/wfc-middle.cer + acpRootCert: /opt/wfc/conf/cert/unionpay/wfc-root.cer + frontUrl: https://gateway.test.95516.com/gateway/api/frontTransReq.do + backUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/notify + signMethod: 01 + version: 5.1.0 + channelType: 07 + accessType: 0 + currencyCode: 156 + bizType: 000201 + txnType: 01 + txnSubType: 01 + payTimeout: 30 + queryTimeout: 30 + refundTimeout: 30 + frontFailUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/fail + frontSuccessUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/success + frontBackUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/front + backBackUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/back + frontNotifyUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/front/notify + backNotifyUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/back/notify + refundNotifyUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/refund/notify + queryNotifyUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/query/notify + refundSuccessUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/refund/success + refundFailUrl: http://wfc-gateway:${GATEWAY_SERVER_PORT:8080}/unionpay/refund/fail + querySuccessUrl: \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/AliPayMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/AliPayMapper.xml deleted file mode 100644 index 6542d0b..0000000 --- a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/AliPayMapper.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - select d.dept_id, d.parent_id, d.ancestors, d.remark, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time - from sys_dept d - - - - - - insert into sys_dept( - dept_id, - parent_id, - dept_name, - ancestors, - order_num, - leader, - phone, - email, - status, - create_by, - remark, - create_time - )values( - #{deptId}, - #{parentId}, - #{deptName}, - #{ancestors}, - #{orderNum}, - #{leader}, - #{phone}, - #{email}, - #{status}, - #{createBy}, - #{remark}, - sysdate() - ) - - - - update sys_dept - - parent_id = #{parentId}, - dept_name = #{deptName}, - ancestors = #{ancestors}, - order_num = #{orderNum}, - leader = #{leader}, - phone = #{phone}, - email = #{email}, - status = #{status}, - update_by = #{updateBy}, - remark = #{remark}, - update_time = sysdate() - - where dept_id = #{id} - - - - update sys_dept set del_flag = '2' where dept_id = #{id} - - - \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/CreditCardMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/CreditCardMapper.xml deleted file mode 100644 index 64661f1..0000000 --- a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/CreditCardMapper.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark - from sys_config - - - - - - - and config_id = #{configId} - - - and config_key = #{configKey} - - - - - - - - - - - - - - insert into sys_config ( - config_name, - config_key, - config_value, - config_type, - create_by, - remark, - create_time - )values( - #{configName}, - #{configKey}, - #{configValue}, - #{configType}, - #{createBy}, - #{remark}, - sysdate() - ) - - - - update sys_config - - config_name = #{configName}, - config_key = #{configKey}, - config_value = #{configValue}, - config_type = #{configType}, - update_by = #{updateBy}, - remark = #{remark}, - update_time = sysdate() - - where config_id = #{configId} - - - - delete from credit_card where user_id = #{userId} - - - - delete from sys_config where config_id in - - #{configId} - - - - \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/PayPalMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/PayPalMapper.xml deleted file mode 100644 index d8d4838..0000000 --- a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/PayPalMapper.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - select d.dept_id, d.parent_id, d.ancestors, d.remark, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time - from sys_dept d - - - - - - insert into sys_dept( - dept_id, - parent_id, - dept_name, - ancestors, - order_num, - leader, - phone, - email, - status, - create_by, - remark, - create_time - )values( - #{deptId}, - #{parentId}, - #{deptName}, - #{ancestors}, - #{orderNum}, - #{leader}, - #{phone}, - #{email}, - #{status}, - #{createBy}, - #{remark}, - sysdate() - ) - - - - update sys_dept - - parent_id = #{parentId}, - dept_name = #{deptName}, - ancestors = #{ancestors}, - order_num = #{orderNum}, - leader = #{leader}, - phone = #{phone}, - email = #{email}, - status = #{status}, - update_by = #{updateBy}, - remark = #{remark}, - update_time = sysdate() - - where dept_id = #{id} - - - - update sys_dept set del_flag = '2' where dept_id = #{id} - - - \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardPaymentMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardPaymentMapper.xml new file mode 100644 index 0000000..8990187 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardPaymentMapper.xml @@ -0,0 +1,15 @@ + + + + + + INSERT INTO u_credit_card_payment (transaction_id, user_id, order_id, amount, currency, status, payment_time, card_type, card_holder_name, billing_address, card_last_four, gateway_response) + VALUES (#{transactionId}, #{userId}, #{orderId}, #{amount}, #{currency}, #{status}, #{paymentTime}, #{cardType}, #{cardHolderName}, #{billingAddress}, #{cardLastFour}, #{gatewayResponse}) + + + + + + \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardTokenMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardTokenMapper.xml new file mode 100644 index 0000000..85b9996 --- /dev/null +++ b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/UCreditCardTokenMapper.xml @@ -0,0 +1,14 @@ + + + + + + INSERT INTO u_credit_card_token (user_id, token, card_last_four, card_type, created_at) + VALUES (#{userId}, #{token}, #{cardLastFour}, #{cardType}, #{createdAt}) + + + + + \ No newline at end of file diff --git a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/WxPayMapper.xml b/wfc-modules/wfc-payment/src/main/resources/mapper/payment/WxPayMapper.xml deleted file mode 100644 index 54181a7..0000000 --- a/wfc-modules/wfc-payment/src/main/resources/mapper/payment/WxPayMapper.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - select d.dept_id, d.parent_id, d.ancestors, d.remark, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time - from sys_dept d - - - - - - insert into sys_dept( - dept_id, - parent_id, - dept_name, - ancestors, - order_num, - leader, - phone, - email, - status, - create_by, - remark, - create_time - )values( - #{deptId}, - #{parentId}, - #{deptName}, - #{ancestors}, - #{orderNum}, - #{leader}, - #{phone}, - #{email}, - #{status}, - #{createBy}, - #{remark}, - sysdate() - ) - - - - update sys_dept - - parent_id = #{parentId}, - dept_name = #{deptName}, - ancestors = #{ancestors}, - order_num = #{orderNum}, - leader = #{leader}, - phone = #{phone}, - email = #{email}, - status = #{status}, - update_by = #{updateBy}, - remark = #{remark}, - update_time = sysdate() - - where dept_id = #{id} - - - - update sys_dept set del_flag = '2' where dept_id = #{id} - - - \ No newline at end of file