| | |
| | | package com.tourguide.common.huifu.biz; |
| | | |
| | | import com.alibaba.fastjson.JSONObject; |
| | | import com.huifu.bspay.sdk.opps.core.net.BasePayRequest; |
| | | import com.tourguide.common.huifu.properties.HuiFuProperties; |
| | | import org.slf4j.Logger; |
| | | import org.slf4j.LoggerFactory; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.http.*; |
| | | import org.springframework.stereotype.Component; |
| | | import org.springframework.web.client.RestTemplate; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.security.KeyFactory; |
| | | import java.security.PrivateKey; |
| | | import java.security.Signature; |
| | | import java.security.spec.PKCS8EncodedKeySpec; |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Base64; |
| | | import java.util.Date; |
| | | import java.math.RoundingMode; |
| | | import java.util.HashMap; |
| | | import java.util.Map; |
| | | |
| | | /** |
| | | * 斗拱主动提现业务。 |
| | | * |
| | | * 备注:该类只向斗拱发起提现,导游余额扣减、提现单状态变更应在本项目业务服务中幂等完成。 |
| | | */ |
| | | /** 斗拱主动取现和出金交易查询。 */ |
| | | @Component |
| | | public class DouGongWithdrawBiz { |
| | | private static final Logger log = LoggerFactory.getLogger(DouGongWithdrawBiz.class); |
| | | |
| | | @Autowired |
| | | private HuiFuProperties huiFuProperties; |
| | | @Autowired |
| | | @Qualifier("huifuRestTemplate") |
| | | private RestTemplate restTemplate; |
| | | private final HuiFuProperties huiFuProperties; |
| | | |
| | | private static final String ENCASHMENT_URL = "https://api.huifu.com/v2/trade/settlement/encashment"; |
| | | /** |
| | | * 主动取现接口。 |
| | | * |
| | | * @param cashAmt 取现金额,单位元 |
| | | * @param huifuId 取现方汇付 ID |
| | | * @param tokenNo 绑定取现卡后返回的取现卡序列号 |
| | | * @return 斗拱响应结果 |
| | | */ |
| | | public JSONObject encashment(BigDecimal cashAmt, String huifuId, String tokenNo) { |
| | | public DouGongWithdrawBiz(HuiFuProperties huiFuProperties) { |
| | | this.huiFuProperties = huiFuProperties; |
| | | } |
| | | |
| | | public JSONObject encashment(BigDecimal cashAmt, String huifuId, String tokenNo, |
| | | String requestSequence, String requestDate) { |
| | | try { |
| | | log.info("开始调用斗拱提现接口,取现方ID: {}, 取现金额: {}", huifuId, cashAmt); |
| | | |
| | | JSONObject data = new JSONObject(); |
| | | SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd"); |
| | | String reqDate = dateFormat.format(new Date()); |
| | | |
| | | data.put("req_date", reqDate); |
| | | data.put("req_seq_id", DouGongRequestSupport.generateForHuiFu("TX")); |
| | | data.put("cash_amt", cashAmt.toString()); |
| | | data.put("huifu_id", huifuId); |
| | | // 备注:提现到账周期先固定 D1,后续如果后台提供提现规则,再从配置或提现申请中读取。 |
| | | data.put("into_acct_date_type", "D1"); |
| | | data.put("token_no", tokenNo); |
| | | data.put("notify_url", huiFuProperties.getCallbackPath() + "/base/dougong/encashment"); |
| | | |
| | | String dataStr = data.toJSONString(); |
| | | log.info("业务参数data: {}", dataStr); |
| | | |
| | | JSONObject requestJson = new JSONObject(); |
| | | requestJson.put("sys_id", huiFuProperties.getSysId()); |
| | | requestJson.put("product_id", huiFuProperties.getProductId()); |
| | | requestJson.put("data", dataStr); |
| | | |
| | | // 备注:主动提现接口按完整报文签名,不复用支付创建接口只签 data 的逻辑。 |
| | | String signData = requestJson.toJSONString(); |
| | | log.info("待签名报文: {}", signData); |
| | | |
| | | String signature = sign(signData, huiFuProperties.getRsaPrivateKey()); |
| | | if (signature == null) { |
| | | log.error("签名生成失败"); |
| | | return createErrorResult("签名生成失败"); |
| | | } |
| | | |
| | | requestJson.put("sign", signature); |
| | | |
| | | HttpHeaders headers = new HttpHeaders(); |
| | | headers.setContentType(MediaType.APPLICATION_JSON); |
| | | |
| | | HttpEntity<String> requestEntity = new HttpEntity<>(requestJson.toJSONString(), headers); |
| | | |
| | | log.info("发送斗拱提现请求到: {}", ENCASHMENT_URL); |
| | | ResponseEntity<String> response = restTemplate.exchange( |
| | | ENCASHMENT_URL, |
| | | HttpMethod.POST, |
| | | requestEntity, |
| | | String.class |
| | | ); |
| | | |
| | | log.info("斗拱提现接口响应状态: {}, 响应内容: {}", response.getStatusCode(), response.getBody()); |
| | | |
| | | // 解析响应 |
| | | JSONObject result = JSONObject.parseObject(response.getBody()); |
| | | return result; |
| | | |
| | | } catch (Exception e) { |
| | | log.error("调用斗拱提现接口异常: {}", e.getMessage(), e); |
| | | return createErrorResult("调用接口异常: " + e.getMessage()); |
| | | DouGongRequestSupport.initBasePay(huiFuProperties); |
| | | Map<String, Object> params = new HashMap<>(); |
| | | params.put("req_date", requestDate); |
| | | params.put("req_seq_id", requestSequence); |
| | | params.put("cash_amt", cashAmt.setScale(2, RoundingMode.HALF_UP).toString()); |
| | | params.put("huifu_id", huifuId); |
| | | params.put("into_acct_date_type", "D1"); |
| | | params.put("token_no", tokenNo); |
| | | params.put("enchashment_channel", "00"); |
| | | params.put("notify_url", callbackBaseUrl() + "/base/dougong/encashment"); |
| | | log.info("调用斗拱取现接口,reqSeqId={},huifuId={},cashAmt={}", |
| | | requestSequence, huifuId, cashAmt); |
| | | return json(BasePayRequest.requestBasePay( |
| | | "v2/trade/settlement/encashment", params, null, false)); |
| | | } catch (Exception exception) { |
| | | log.error("调用斗拱取现接口异常,reqSeqId={},原因={}", |
| | | requestSequence, exception.getMessage(), exception); |
| | | return error(exception.getMessage()); |
| | | } |
| | | } |
| | | |
| | | public JSONObject query(String huifuId, String requestDate, String requestSequence) { |
| | | try { |
| | | DouGongRequestSupport.initBasePay(huiFuProperties); |
| | | Map<String, Object> params = new HashMap<>(); |
| | | params.put("huifu_id", huifuId); |
| | | params.put("org_req_date", requestDate); |
| | | params.put("org_req_seq_id", requestSequence); |
| | | return json(BasePayRequest.requestBasePay( |
| | | "v2/trade/settlement/query", params, null, false)); |
| | | } catch (Exception exception) { |
| | | log.warn("查询斗拱取现状态异常,reqSeqId={},原因={}", requestSequence, exception.getMessage()); |
| | | return error(exception.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private JSONObject createErrorResult(String errorMsg) { |
| | | private JSONObject json(Map<String, Object> response) { |
| | | return response == null ? null : JSONObject.parseObject(JSONObject.toJSONString(response)); |
| | | } |
| | | |
| | | private JSONObject error(String message) { |
| | | JSONObject result = new JSONObject(); |
| | | result.put("code", "9999"); |
| | | result.put("msg", errorMsg); |
| | | result.put("msg", message == null ? "斗拱服务调用异常" : message); |
| | | result.put("success", false); |
| | | return result; |
| | | } |
| | | public static String sign(String data, String privateKeyBase64) { |
| | | // Base64 --> Key |
| | | try { |
| | | byte[] bytes = Base64.getDecoder().decode(privateKeyBase64); |
| | | PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(bytes); |
| | | KeyFactory keyFactory; |
| | | keyFactory = KeyFactory.getInstance("RSA"); |
| | | PrivateKey privateKey = keyFactory.generatePrivate(keySpec); |
| | | // Sign |
| | | Signature signature = Signature.getInstance("SHA256WithRSA"); |
| | | signature.initSign(privateKey); |
| | | signature.update(data.getBytes("UTF-8")); |
| | | return Base64.getEncoder().encodeToString(signature.sign()); |
| | | } catch (Exception e) { |
| | | return null; |
| | | } |
| | | |
| | | private String callbackBaseUrl() { |
| | | String value = huiFuProperties.getCallbackPath().trim(); |
| | | while (value.endsWith("/")) value = value.substring(0, value.length() - 1); |
| | | return value; |
| | | } |
| | | } |