记得上下班打卡 | git大法好,push需谨慎

Commit 3ad34a00 authored by 姜秀龙's avatar 姜秀龙

Merge remote-tracking branch 'origin/dev-1.6-shouqianba' into dev-1.6-shouqianba

parents 254ed70b 9fc041df
...@@ -3,7 +3,11 @@ package com.liquidnet.service.goblin.param.shouqianba.request; ...@@ -3,7 +3,11 @@ package com.liquidnet.service.goblin.param.shouqianba.request;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data @Data
public class CommonRequest { public class CommonRequest {
...@@ -11,6 +15,8 @@ public class CommonRequest { ...@@ -11,6 +15,8 @@ public class CommonRequest {
@Data @Data
@ApiModel(value = "商城信息") @ApiModel(value = "商城信息")
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
@NoArgsConstructor
public static class Mall{ public static class Mall{
@ApiModelProperty(required = true, value = "商城ID") @ApiModelProperty(required = true, value = "商城ID")
...@@ -23,6 +29,8 @@ public class CommonRequest { ...@@ -23,6 +29,8 @@ public class CommonRequest {
@Data @Data
@ApiModel(value = "卖家信息/商户信息") @ApiModel(value = "卖家信息/商户信息")
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
@NoArgsConstructor
public static class Seller { public static class Seller {
@ApiModelProperty(required = true, value = "商户ID") @ApiModelProperty(required = true, value = "商户ID")
private String merchantId; private String merchantId;
...@@ -36,6 +44,8 @@ public class CommonRequest { ...@@ -36,6 +44,8 @@ public class CommonRequest {
@Data @Data
@ApiModel("买家信息") @ApiModel("买家信息")
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class Buyer{ public static class Buyer{
@ApiModelProperty(required = true, value = "买家ID(用户唯一标识)") @ApiModelProperty(required = true, value = "买家ID(用户唯一标识)")
...@@ -44,6 +54,8 @@ public class CommonRequest { ...@@ -44,6 +54,8 @@ public class CommonRequest {
@Data @Data
@ApiModel("付款人信息") @ApiModel("付款人信息")
@AllArgsConstructor
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public static class Payer{ public static class Payer{
@ApiModelProperty(required = true, value = "付款人ID") @ApiModelProperty(required = true, value = "付款人ID")
...@@ -53,6 +65,8 @@ public class CommonRequest { ...@@ -53,6 +65,8 @@ public class CommonRequest {
@ApiModel(value = "收单信息") @ApiModel(value = "收单信息")
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@AllArgsConstructor
@NoArgsConstructor
public static class Acquiring{ public static class Acquiring{
@ApiModelProperty(value = "收单号") @ApiModelProperty(value = "收单号")
private String acquiringSn; private String acquiringSn;
...@@ -60,4 +74,36 @@ public class CommonRequest { ...@@ -60,4 +74,36 @@ public class CommonRequest {
@ApiModelProperty(value = "收单密钥") @ApiModelProperty(value = "收单密钥")
private String signature; private String signature;
} }
@Data
@ApiModel(value = "金额构成信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class AmountComposition {
@ApiModelProperty(required = true, value = "金额构成项列表")
private List<CompositionItem> compositionItems;
}
@Data
@ApiModel(value = "金额构成项详情")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class CompositionItem {
@ApiModelProperty(required = true, value = "构成项类目")
private String category;
@ApiModelProperty(required = true, value = "构成项金额")
private Long amount;
}
@Data
@ApiModel(value = "订单信息")
@AllArgsConstructor
@NoArgsConstructor
public static class OrderInfo {
@ApiModelProperty(required = true, value = "订单ID")
private String sn;
@ApiModelProperty(required = true, value = "订单密码")
private String signature;
}
} }
...@@ -12,21 +12,10 @@ public class CouponQueryRequest { ...@@ -12,21 +12,10 @@ public class CouponQueryRequest {
private String appid; private String appid;
@ApiModelProperty(required = true, value = "订单信息") @ApiModelProperty(required = true, value = "订单信息")
private OrderInfo orderID; private CommonRequest.OrderInfo orderID;
@ApiModelProperty(required = true, value = "卖家信息", example = "4") @ApiModelProperty(required = true, value = "卖家信息", example = "4")
private CommonRequest.Seller seller; private CommonRequest.Seller seller;
@Data
@ApiModel(value = "订单信息")
public static class OrderInfo {
@ApiModelProperty(required = true, value = "订单ID")
private String sn;
@ApiModelProperty(required = true, value = "订单密码")
private String signature;
}
} }
...@@ -2,10 +2,11 @@ package com.liquidnet.service.goblin.param.shouqianba.request; ...@@ -2,10 +2,11 @@ package com.liquidnet.service.goblin.param.shouqianba.request;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List; import java.util.List;
import java.util.Map;
@Data @Data
@ApiModel(value = "券退款请求参数") @ApiModel(value = "券退款请求参数")
...@@ -18,7 +19,7 @@ public class CouponRefundRequest { ...@@ -18,7 +19,7 @@ public class CouponRefundRequest {
private CommonRequest.Seller seller; private CommonRequest.Seller seller;
@ApiModelProperty(required = true, value = "订单信息") @ApiModelProperty(required = true, value = "订单信息")
private OrderInfo orderID; private CommonRequest.OrderInfo orderID;
@ApiModelProperty(required = true, value = "申请退款信息不能为空") @ApiModelProperty(required = true, value = "申请退款信息不能为空")
private RefundInfo refundInfo; private RefundInfo refundInfo;
...@@ -29,19 +30,11 @@ public class CouponRefundRequest { ...@@ -29,19 +30,11 @@ public class CouponRefundRequest {
@ApiModelProperty(required = true, value = "退款来源(固定值:EXTERN)") @ApiModelProperty(required = true, value = "退款来源(固定值:EXTERN)")
private String requestSource; private String requestSource;
@Data
@ApiModel(value = "订单信息")
public static class OrderInfo {
@ApiModelProperty(required = true, value = "单号(前置操作后拿到)")
private String sn;
@ApiModelProperty(required = true, value = "密码(前置操作后拿到)")
private String signature;
}
@Data @Data
@ApiModel(value = "退款信息") @ApiModel(value = "退款信息")
@AllArgsConstructor
@NoArgsConstructor
public static class RefundInfo { public static class RefundInfo {
@ApiModelProperty(required = true, value = "金额") @ApiModelProperty(required = true, value = "金额")
private Long applyAmount; private Long applyAmount;
......
...@@ -51,7 +51,7 @@ public class CreateWechatPrepayOrderRequest { ...@@ -51,7 +51,7 @@ public class CreateWechatPrepayOrderRequest {
private String amount; private String amount;
@ApiModelProperty(required = true, value = "金额构成") @ApiModelProperty(required = true, value = "金额构成")
private AmountComposition amountComposition; private CommonRequest.AmountComposition amountComposition;
@ApiModelProperty(required = true, value = "用户身份信息", example = "wzwl") @ApiModelProperty(required = true, value = "用户身份信息", example = "wzwl")
private String identity; private String identity;
...@@ -86,20 +86,5 @@ public class CreateWechatPrepayOrderRequest { ...@@ -86,20 +86,5 @@ public class CreateWechatPrepayOrderRequest {
private String latitude; private String latitude;
} }
@Data
@ApiModel(value = "金额构成信息")
public static class AmountComposition {
@ApiModelProperty(required = true, value = "金额构成项列表")
private List<CompositionItem> compositionItems;
}
@Data
@ApiModel(value = "金额构成项详情")
public static class CompositionItem {
@ApiModelProperty(required = true, value = "构成项类目")
private String category;
@ApiModelProperty(required = true, value = "构成项金额")
private Long amount;
}
} }
package com.liquidnet.service.goblin.param.shouqianba.response.data; package com.liquidnet.service.goblin.param.shouqianba.response.data;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.liquidnet.service.goblin.param.shouqianba.request.CommonRequest;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
...@@ -65,27 +66,10 @@ public class CashierQueryData { ...@@ -65,27 +66,10 @@ public class CashierQueryData {
private List<String> tips; private List<String> tips;
@ApiModelProperty(value = "金额构成") @ApiModelProperty(value = "金额构成")
private AmountComposition amountComposition; private CommonRequest.AmountComposition amountComposition;
} }
@Data
@ApiModel(value = "金额构成信息")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class AmountComposition {
@ApiModelProperty(required = true, value = "金额构成项列表")
private List<CompositionItem> compositionItems;
}
@Data
@ApiModel(value = "金额构成项详情")
@JsonIgnoreProperties(ignoreUnknown = true)
public static class CompositionItem {
@ApiModelProperty(required = true, value = "构成项类目")
private String category;
@ApiModelProperty(required = true, value = "构成项金额")
private Long amount;
}
@Data @Data
@ApiModel(value = "收款方信息") @ApiModel(value = "收款方信息")
......
...@@ -5,6 +5,7 @@ import com.liquidnet.service.goblin.param.shouqianba.request.*; ...@@ -5,6 +5,7 @@ import com.liquidnet.service.goblin.param.shouqianba.request.*;
import com.liquidnet.service.goblin.param.shouqianba.response.data.*; import com.liquidnet.service.goblin.param.shouqianba.response.data.*;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 收钱吧对接接口定义 * 收钱吧对接接口定义
...@@ -25,6 +26,16 @@ public interface IGoblinShouQianBaService { ...@@ -25,6 +26,16 @@ public interface IGoblinShouQianBaService {
*/ */
SettlementCreateData createSettlement(SettlementCreateRequest request); SettlementCreateData createSettlement(SettlementCreateRequest request);
/**
*
* @param mallSn 商城ID
* @param signature 商城密钥
* @param userId 正在用户ID
* @param checkoutItems 商品
* @return
*/
SettlementCreateData createSettlement(String mallSn, String signature, String userId, List<SettlementCreateRequest.CheckoutItem> checkoutItems);
/** /**
* 创建订单 * 创建订单
* *
...@@ -33,6 +44,18 @@ public interface IGoblinShouQianBaService { ...@@ -33,6 +44,18 @@ public interface IGoblinShouQianBaService {
*/ */
OrderCreateData createOrder(OrderCreateRequest request); OrderCreateData createOrder(OrderCreateRequest request);
/**
*
* @param mallSn 商城ID
* @param signature 商城密钥
* @param checkoutItemsId 创建结算明细 返回的 结算项ID
* @param userId 正在用户ID
* @param requestId 请求ID
* @param subject 标题
* @return
*/
OrderCreateData createOrder(String mallSn, String signature, String checkoutItemsId, String userId, String requestId, String subject);
/** /**
* 创建收单 * 创建收单
* 如果上一步创建收单失败 可以使用这个接口重新创建收单 * 如果上一步创建收单失败 可以使用这个接口重新创建收单
...@@ -49,6 +72,15 @@ public interface IGoblinShouQianBaService { ...@@ -49,6 +72,15 @@ public interface IGoblinShouQianBaService {
*/ */
CashierQueryData queryCashier(CashierQueryRequest request); CashierQueryData queryCashier(CashierQueryRequest request);
/**
* 查询收银台
* @param acquiringSn 收单号
* @param acquiringSignature 收单密钥
* @param userId 正在用户ID
* @return
*/
CashierQueryData queryCashier(String acquiringSn, String acquiringSignature, String userId);
/** /**
* 创建微信预支付订单 * 创建微信预支付订单
* *
...@@ -57,6 +89,30 @@ public interface IGoblinShouQianBaService { ...@@ -57,6 +89,30 @@ public interface IGoblinShouQianBaService {
*/ */
CreateWechatPrepayOrderData createWechatPrepayOrder(CreateWechatPrepayOrderRequest request); CreateWechatPrepayOrderData createWechatPrepayOrder(CreateWechatPrepayOrderRequest request);
/**
* 创建微信预支付订单
*
* @param acquiringSn 收单号
* @param signature 收单签名
* @param userId 正在用户ID
* @param amount 支付金额
* @param requestSn 支付请求号
* @param payTool 支付工具代码
* @param channelExt 通道扩展参数
* @param selectedSignature 支付工具签名
* @param seq 序列号 上一步seq(查询收银台)
* @return
*/
CreateWechatPrepayOrderData createWechatPrepayOrder(String acquiringSn,
String signature,
String userId,
String amount,
String requestSn,
CashierQueryData.PayTool payTool,
Map<String, Object> channelExt,
String selectedSignature,
String seq);
/** /**
* 查询券码 * 查询券码
* *
...@@ -65,6 +121,14 @@ public interface IGoblinShouQianBaService { ...@@ -65,6 +121,14 @@ public interface IGoblinShouQianBaService {
*/ */
CouponQueryData queryCoupon(CouponQueryRequest request); CouponQueryData queryCoupon(CouponQueryRequest request);
/**
* 查询券码
* @param sn 订单ID
* @param signature 订单密码
* @return
*/
CouponQueryData queryCoupon(String sn, String signature);
/** /**
* 券码状态同步 * 券码状态同步
* *
...@@ -73,6 +137,21 @@ public interface IGoblinShouQianBaService { ...@@ -73,6 +137,21 @@ public interface IGoblinShouQianBaService {
*/ */
boolean syncCouponStatus(CouponStatusSyncRequest request); boolean syncCouponStatus(CouponStatusSyncRequest request);
/**
* 券码状态同步
* @param redeemMerchantId 用户ID(自定义,核销用户id)
* @param voucherNos 券号
* @param redeemExternalOrderSn 外部单号(${AppCode}+id,AppCode需要分配)
* @param clientSn 核销终端号(自定义)
* @param status 券使用状态(0未核销,1已核销)
* @return
*/
boolean syncCouponStatus(String redeemMerchantId,
List<String> voucherNos,
String redeemExternalOrderSn,
String clientSn,
Byte status);
/** /**
* 券退款 * 券退款
* *
...@@ -81,6 +160,24 @@ public interface IGoblinShouQianBaService { ...@@ -81,6 +160,24 @@ public interface IGoblinShouQianBaService {
*/ */
CouponRefundData refundCoupon(CouponRefundRequest request); CouponRefundData refundCoupon(CouponRefundRequest request);
/**
* @param sn 单号(前置操作后拿到)
* @param signature 密码(前置操作后拿到)
* @param applyAmount 金额
* @param type 退款类型(1商品 2金额)
* @param item
* @param refundReason 退款原因
* @param requestId 退款请求id(${AppCode}+id)
* @return
*/
CouponRefundData refundCoupon(String sn,
String signature,
Long applyAmount,
Byte type,
List<CouponRefundRequest.RefundItem> item,
String refundReason,
String requestId);
/** /**
* 商城列表接口 * 商城列表接口
* *
...@@ -89,6 +186,14 @@ public interface IGoblinShouQianBaService { ...@@ -89,6 +186,14 @@ public interface IGoblinShouQianBaService {
*/ */
List<MallListQueryData> queryMallList(MallListQueryRequest request); List<MallListQueryData> queryMallList(MallListQueryRequest request);
/**
*
* @param endCursor 结束游标 上一页的结束游标(首次查询传 null)
* @param count 查询数量 每页返回的最大订单数
* @return
*/
List<MallListQueryData> queryMallList(String endCursor, Integer count);
/** /**
* 商城商品接口 * 商城商品接口
* *
...@@ -97,6 +202,14 @@ public interface IGoblinShouQianBaService { ...@@ -97,6 +202,14 @@ public interface IGoblinShouQianBaService {
*/ */
List<MallProductsQueryData> queryMallProducts(MallProductsQueryRequest request); List<MallProductsQueryData> queryMallProducts(MallProductsQueryRequest request);
/**
* 商城商品接口
* @param mallSn 商城ID
* @param signature 商城密码
* @return
*/
List<MallProductsQueryData> queryMallProducts(String mallSn, String signature);
/** /**
* 验签 * 验签
* @param callbackParams * @param callbackParams
......
...@@ -26,7 +26,9 @@ import java.security.PublicKey; ...@@ -26,7 +26,9 @@ import java.security.PublicKey;
import java.security.Signature; import java.security.Signature;
import java.security.spec.X509EncodedKeySpec; import java.security.spec.X509EncodedKeySpec;
import java.util.Base64; import java.util.Base64;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* 收钱吧对接实现类 * 收钱吧对接实现类
...@@ -40,12 +42,18 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -40,12 +42,18 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
private final ShouqianbaProperties shouqianbaProperties; private final ShouqianbaProperties shouqianbaProperties;
private final CommonRequest.Seller cachedSeller;
public GoblinShouQianBaServiceImpl(ShouqianbaProperties properties) { public GoblinShouQianBaServiceImpl(ShouqianbaProperties properties) {
this.shouqianbaProperties = properties; this.shouqianbaProperties = properties;
if (this.shouqianbaProperties == null) { if (this.shouqianbaProperties == null) {
throw new IllegalArgumentException("找不到应用配置"); throw new IllegalArgumentException("找不到应用配置");
} }
// 商户信息来自配置,整个生命周期内不变,提前构建并缓存
this.cachedSeller = new CommonRequest.Seller();
this.cachedSeller.setMerchantId(properties.getMerchantId());
this.cachedSeller.setMerchantUserId(properties.getMerchantUserId());
this.cachedSeller.setRole(properties.getRole());
} }
private static final ObjectMapper objectMapper; private static final ObjectMapper objectMapper;
...@@ -74,6 +82,30 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -74,6 +82,30 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/mall/createCheckoutItems", "创建结算明细", request, SettlementCreateResponse.class); return executeShouQianBaRequest("/optimus/core/mall/createCheckoutItems", "创建结算明细", request, SettlementCreateResponse.class);
} }
@Override
public SettlementCreateData createSettlement(String mallSn, String signature, String userId, List<SettlementCreateRequest.CheckoutItem> checkoutItems) {
SettlementCreateRequest request = new SettlementCreateRequest();
request.setAppid(shouqianbaProperties.getAppId());
request.setMallID(buildMall(mallSn, signature));
request.setSeller(cachedSeller);
request.setBuyer(buildBuyer(userId));
request.setCheckoutItems(checkoutItems);
return createSettlement(request);
}
private CommonRequest.Mall buildMall(String mallSn, String signature) {
CommonRequest.Mall mall = new CommonRequest.Mall();
mall.setMallSn(mallSn);
mall.setSignature(signature);
return mall;
}
private CommonRequest.Buyer buildBuyer(String userId) {
CommonRequest.Buyer buyer = new CommonRequest.Buyer();
buyer.setBuyerId(userId);
return buyer;
}
/** /**
* 创建订单 * 创建订单
* *
...@@ -85,6 +117,19 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -85,6 +117,19 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/order/placeOrder", "创建订单", request, OrderCreateResponse.class); return executeShouQianBaRequest("/optimus/core/order/placeOrder", "创建订单", request, OrderCreateResponse.class);
} }
@Override
public OrderCreateData createOrder(String mallSn, String signature, String checkoutItemsId, String userId, String requestId, String subject) {
OrderCreateRequest orderCreateRequest = new OrderCreateRequest();
orderCreateRequest.setAppid(shouqianbaProperties.getAppId());
orderCreateRequest.setMallID(buildMall(mallSn, signature));
orderCreateRequest.setSeller(cachedSeller);
orderCreateRequest.setCheckoutItemsId(checkoutItemsId);
orderCreateRequest.setBuyer(buildBuyer(userId));
orderCreateRequest.setRequestId(requestId);
orderCreateRequest.setSubject(subject);
return createOrder(orderCreateRequest);
}
/** /**
* 创建收单 * 创建收单
* 如果上一步创建收单失败 可以使用这个接口重新创建收单 * 如果上一步创建收单失败 可以使用这个接口重新创建收单
...@@ -107,6 +152,22 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -107,6 +152,22 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/cashier/queryCashierPage", "查询收银台", request, CashierQueryResponse.class); return executeShouQianBaRequest("/optimus/core/cashier/queryCashierPage", "查询收银台", request, CashierQueryResponse.class);
} }
@Override
public CashierQueryData queryCashier(String acquiringSn, String acquiringSignature, String userId) {
CashierQueryRequest.PaymentEnv paymentEnv = new CashierQueryRequest.PaymentEnv();
paymentEnv.setClient("wechat");
CashierQueryRequest cashierQueryRequest = new CashierQueryRequest();
cashierQueryRequest.setAppid(shouqianbaProperties.getAppId());
cashierQueryRequest.setSeller(cachedSeller);
cashierQueryRequest.setPaymentMode(4);
cashierQueryRequest.setPaymentEnv(paymentEnv);
cashierQueryRequest.setPayer(new CommonRequest.Payer(userId));
cashierQueryRequest.setAcquiringInfo(new CommonRequest.Acquiring(acquiringSn, acquiringSignature));
return queryCashier(cashierQueryRequest);
}
/** /**
* 创建微信预支付订单 * 创建微信预支付订单
* *
...@@ -118,6 +179,45 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -118,6 +179,45 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/cashier/proxyPreCreatedPay", "创建微信预支付订单", request, CreateWechatPrepayOrderResponse.class); return executeShouQianBaRequest("/optimus/core/cashier/proxyPreCreatedPay", "创建微信预支付订单", request, CreateWechatPrepayOrderResponse.class);
} }
@Override
public CreateWechatPrepayOrderData createWechatPrepayOrder(String acquiringSn,
String signature,
String userId,
String amount,
String requestSn,
CashierQueryData.PayTool payTool,
Map<String, Object> channelExt,
String selectedSignature,
String seq) {
CreateWechatPrepayOrderRequest orderRequest = new CreateWechatPrepayOrderRequest();
orderRequest.setAppid(shouqianbaProperties.getAppId());
orderRequest.setSeller(cachedSeller);
orderRequest.setAcquiringSn(acquiringSn);
orderRequest.setSignature(signature);
orderRequest.setUsingPayTools(buildUsingPayTools(requestSn, payTool, channelExt, amount, userId));
orderRequest.setSelectedSignature(selectedSignature);
orderRequest.setSeq(seq);
return createWechatPrepayOrder(orderRequest);
}
private List<CreateWechatPrepayOrderRequest.UsingPayTool> buildUsingPayTools(String requestSn,
CashierQueryData.PayTool payTool,
Map<String, Object> channelExt,
String amount,
String userId) {
CreateWechatPrepayOrderRequest.UsingPayTool tool = new CreateWechatPrepayOrderRequest.UsingPayTool();
tool.setId(payTool.getId());
tool.setPayTool(Integer.valueOf(payTool.getCode()));
tool.setPayMode(4); // TODO 需要确认
tool.setAmount(amount);
tool.setIdentity(userId);
tool.setAmountComposition(payTool.getAmountComposition());
tool.setRequestSn(requestSn);
tool.setChannelExt(channelExt);
return Collections.singletonList(tool);
}
/** /**
* 查询券码 * 查询券码
* *
...@@ -129,6 +229,15 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -129,6 +229,15 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/voucher/queryOrderVoucherList", "查询优惠券", request, CouponQueryResponse.class); return executeShouQianBaRequest("/optimus/core/voucher/queryOrderVoucherList", "查询优惠券", request, CouponQueryResponse.class);
} }
@Override
public CouponQueryData queryCoupon(String sn, String signature) {
CouponQueryRequest couponQueryRequest = new CouponQueryRequest();
couponQueryRequest.setAppid(shouqianbaProperties.getAppId());
couponQueryRequest.setSeller(cachedSeller);
couponQueryRequest.setOrderID(new CommonRequest.OrderInfo(sn, signature));
return queryCoupon(couponQueryRequest);
}
/** /**
* 券码状态同步 * 券码状态同步
* *
...@@ -141,6 +250,19 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -141,6 +250,19 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return response != null && Boolean.TRUE.equals(response.getSuccess()); return response != null && Boolean.TRUE.equals(response.getSuccess());
} }
@Override
public boolean syncCouponStatus(String redeemMerchantId, List<String> voucherNos, String redeemExternalOrderSn, String clientSn, Byte status) {
CouponStatusSyncRequest couponStatusSyncRequest = new CouponStatusSyncRequest();
couponStatusSyncRequest.setAppid(shouqianbaProperties.getAppId());
couponStatusSyncRequest.setVoucherNos(voucherNos);
couponStatusSyncRequest.setRedeemSource("EXTERN");
couponStatusSyncRequest.setRedeemExternalOrderSn(redeemExternalOrderSn);
couponStatusSyncRequest.setRedeemMerchantId(redeemMerchantId);
couponStatusSyncRequest.setClientSn(clientSn);
couponStatusSyncRequest.setStatus(status);
return syncCouponStatus(couponStatusSyncRequest);
}
/** /**
* 券退款 * 券退款
* *
...@@ -152,6 +274,24 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -152,6 +274,24 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/aftersale/applyRefund", "退款优惠券", request, CouponRefundResponse.class); return executeShouQianBaRequest("/optimus/core/aftersale/applyRefund", "退款优惠券", request, CouponRefundResponse.class);
} }
@Override
public CouponRefundData refundCoupon(String sn,
String signature,
Long applyAmount,
Byte type,
List<CouponRefundRequest.RefundItem> item,
String refundReason,
String requestId) {
CouponRefundRequest couponRefundRequest = new CouponRefundRequest();
couponRefundRequest.setAppid(shouqianbaProperties.getAppId());
couponRefundRequest.setSeller(cachedSeller);
couponRefundRequest.setOrderID(new CommonRequest.OrderInfo(sn, signature));
couponRefundRequest.setRefundInfo(new CouponRefundRequest.RefundInfo(applyAmount, type, item, refundReason));
couponRefundRequest.setRequestId(requestId);
couponRefundRequest.setRequestSource("EXTERN");
return refundCoupon(couponRefundRequest);
}
/** /**
* 商城列表接口 * 商城列表接口
* *
...@@ -163,6 +303,29 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -163,6 +303,29 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/open/mall/queryMallList", "查询门店列表", request, MallListQueryResponse.class); return executeShouQianBaRequest("/optimus/core/open/mall/queryMallList", "查询门店列表", request, MallListQueryResponse.class);
} }
@Override
public List<MallListQueryData> queryMallList(String endCursor, Integer count) {
MallListQueryRequest.Filter filter = new MallListQueryRequest.Filter();
filter.setSeller(cachedSeller);
MallListQueryRequest.Cursor cursor = new MallListQueryRequest.Cursor();
cursor.setCursorField("id");
cursor.setEndCursor(endCursor);
cursor.setCount(count);
MallListQueryRequest.Sort sort = new MallListQueryRequest.Sort();
sort.setSortField("id");
sort.setSort("DESC");
MallListQueryRequest mallListQueryRequest = new MallListQueryRequest();
mallListQueryRequest.setAppid(shouqianbaProperties.getAppId());
mallListQueryRequest.setFilter(filter);
mallListQueryRequest.setCursor(cursor);
mallListQueryRequest.setSort(sort);
return queryMallList(mallListQueryRequest);
}
/** /**
* 商城商品接口 * 商城商品接口
* *
...@@ -174,6 +337,15 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -174,6 +337,15 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
return executeShouQianBaRequest("/optimus/core/open/mall/queryMallProducts", "查询门店商品", request, MallProductsQueryResponse.class); return executeShouQianBaRequest("/optimus/core/open/mall/queryMallProducts", "查询门店商品", request, MallProductsQueryResponse.class);
} }
@Override
public List<MallProductsQueryData> queryMallProducts(String mallSn, String signature) {
MallProductsQueryRequest request = new MallProductsQueryRequest();
request.setAppid(shouqianbaProperties.getAppId());
request.setSeller(cachedSeller);
request.setMallID(new CommonRequest.Mall(mallSn, signature));
return queryMallProducts(request);
}
/** /**
* 发送HTTP请求并返回Response对象 * 发送HTTP请求并返回Response对象
*/ */
...@@ -183,14 +355,7 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService { ...@@ -183,14 +355,7 @@ public class GoblinShouQianBaServiceImpl implements IGoblinShouQianBaService {
log.info("[收钱吧] {}, 请求URL: {}, 请求参数: {}", businessName, url, request); log.info("[收钱吧] {}, 请求URL: {}, 请求参数: {}", businessName, url, request);
try { try {
// body进行序列化成TreeMap以后 最后转成json(为了排序稳定)
// TreeMap<String, Object> map = objectMapper.convertValue(
// request,
// new TypeReference<TreeMap<String, Object>>() {
// }
// );
String requestBody = objectMapper.writeValueAsString(request); String requestBody = objectMapper.writeValueAsString(request);
// 去除可能存在的空格和换行符,以保证签名通过
log.info("request body: {}", requestBody); log.info("request body: {}", requestBody);
// 构建请求头(添加签名参数) // 构建请求头(添加签名参数)
......
...@@ -3,6 +3,7 @@ package com.liquidnet.service.goblin.service.impl; ...@@ -3,6 +3,7 @@ package com.liquidnet.service.goblin.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.liquidnet.commons.lang.util.IDGenerator; import com.liquidnet.commons.lang.util.IDGenerator;
import com.liquidnet.service.base.ResponseDto; import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.goblin.config.properties.ShouqianbaProperties;
import com.liquidnet.service.goblin.dto.vo.*; import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.entity.GoblinSqbPerformanceGoods; import com.liquidnet.service.goblin.entity.GoblinSqbPerformanceGoods;
import com.liquidnet.service.goblin.mapper.GoblinSqbPerformanceGoodsMapper; import com.liquidnet.service.goblin.mapper.GoblinSqbPerformanceGoodsMapper;
...@@ -14,8 +15,8 @@ import com.liquidnet.service.goblin.param.shouqianba.response.data.*; ...@@ -14,8 +15,8 @@ import com.liquidnet.service.goblin.param.shouqianba.response.data.*;
import com.liquidnet.service.goblin.service.IGoblinShouQianBaService; import com.liquidnet.service.goblin.service.IGoblinShouQianBaService;
import com.liquidnet.service.goblin.service.IGoblinSqbOrderService; import com.liquidnet.service.goblin.service.IGoblinSqbOrderService;
import com.liquidnet.service.goblin.util.GoblinRedisUtils; import com.liquidnet.service.goblin.util.GoblinRedisUtils;
import com.liquidnet.service.goblin.util.GoblinSqbConvertUtils;
import com.liquidnet.service.goblin.util.GoblinSqbRedisUtils; import com.liquidnet.service.goblin.util.GoblinSqbRedisUtils;
import com.liquidnet.service.goblin.config.properties.ShouqianbaProperties;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -55,6 +56,9 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -55,6 +56,9 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
@Autowired @Autowired
private ShouqianbaProperties shouqianbaProperties; private ShouqianbaProperties shouqianbaProperties;
@Autowired
private GoblinSqbConvertUtils sqbConvertUtils;
// ================================ 创建订单 ================================ // ================================ 创建订单 ================================
@Override @Override
...@@ -91,67 +95,76 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -91,67 +95,76 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
} }
log.info("[收钱吧下单] 扣减库存成功,skuId={}, remaining={}", skuId, remaining); log.info("[收钱吧下单] 扣减库存成功,skuId={}, remaining={}", skuId, remaining);
// Step 4: 构建收钱吧公共请求参数 GoblinGoodsSkuInfoVo skuVo = goblinRedisUtils.getGoodsSkuInfoVo(skuId);
CommonRequest.Seller seller = buildSeller(); if (skuVo == null) {
log.error("[收钱吧下单] 未找到商品, skuId: {}, spuId: {}", skuId, spuId);
}
if (!skuId.equals(skuVo.getSkuId())) {
return ResponseDto.failure("下单失败");
}
// Step 4.1: createSettlement → 得 checkoutItemsId //TODO 获取该商品对应的商城的编号和密码
// TODO: 从商品数据中获取价格、图片、标题等信息填充 CheckoutItem Map<String, String> sqbInfoMap = sqbConvertUtils.getSqbInfoByzhengzaiSkuIdAndSpuId(skuId, spuId);
SettlementCreateRequest settlementReq = new SettlementCreateRequest();
settlementReq.setAppid(shouqianbaProperties.getAppId()); SettlementCreateData settlementData = goblinShouQianBaService.createSettlement(
settlementReq.setSeller(seller); sqbInfoMap.get("mallSn"),
CommonRequest.Buyer buyer = new CommonRequest.Buyer(); sqbInfoMap.get("signature"),
buyer.setBuyerId(userId); userId,
settlementReq.setBuyer(buyer); Collections.singletonList(buildSqbCheckOutItem(skuVo, quantity, sqbInfoMap.get("sqbSkuId"), sqbInfoMap.get("sqbSpuId"))));
// TODO: settlementReq.setCheckoutItems(...); settlementReq.setAmount(...);
SettlementCreateData settlementData = goblinShouQianBaService.createSettlement(settlementReq);
if (settlementData == null) { if (settlementData == null) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity); goblinRedisUtils.incrSkuStock(null, skuId, quantity);
return ResponseDto.failure("创建结算明细失败"); return ResponseDto.failure("创建结算明细失败");
} }
String checkoutItemsId = settlementData.getCheckoutItemsId(); final String checkoutItemsId = settlementData.getCheckoutItemsId();
log.info("[收钱吧下单] createSettlement 成功,checkoutItemsId={}", checkoutItemsId); log.info("[收钱吧下单] 创建结算明细成功,checkoutItemsId={}", checkoutItemsId);
// Step 4.2: createOrder → 得 sqbOrderSn + sqbOrderSignature + sqbAcquiringSn // Step 4.2: createOrder → 得 sqbOrderSn + sqbOrderSignature + sqbAcquiringSn
OrderCreateRequest orderReq = new OrderCreateRequest(); OrderCreateData orderData = goblinShouQianBaService.createOrder(
orderReq.setAppid(shouqianbaProperties.getAppId()); sqbInfoMap.get("mallSn"),
orderReq.setSeller(seller); sqbInfoMap.get("signature"),
orderReq.setCheckoutItemsId(checkoutItemsId); checkoutItemsId,
orderReq.setBuyer(buyer); userId,
orderReq.setRequestId(IDGenerator.nextSnowId()); "", // TODO 构建请求ID
orderReq.setSubject("收钱吧商品"); "" // TODO 构建标题
);
OrderCreateData orderData = goblinShouQianBaService.createOrder(orderReq);
if (orderData == null) { if (orderData == null) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity); goblinRedisUtils.incrSkuStock(null, skuId, quantity);
return ResponseDto.failure("创建收钱吧订单失败"); return ResponseDto.failure("创建收钱吧订单失败");
} }
String sqbOrderSn = orderData.getOrderSn(); String sqbOrderSn = orderData.getOrderSn(); // 订单编号
String sqbOrderSignature = orderData.getOrderSignature(); String sqbOrderSignature = orderData.getOrderSignature(); // 订单密钥
String sqbAcquiringSn = orderData.getAcquiring() != null ? orderData.getAcquiring().getAcquiringSn() : null; String sqbAcquiringSn = orderData.getAcquiring() != null ? orderData.getAcquiring().getAcquiringSn() : null; // 收单号
log.info("[收钱吧下单] createOrder 成功,sqbOrderSn={}, sqbAcquiringSn={}", sqbOrderSn, sqbAcquiringSn); String sqbAcquiringSign = orderData.getAcquiring() != null ? orderData.getAcquiring().getSignature() : null; // 收单密钥
log.info("[收钱吧下单] 创建订单成功,订单编号={}, 收单号={}", sqbOrderSn, sqbAcquiringSn);
// Step 4.3: queryCashier → 得 selectedSignature + seq // Step 4.3: queryCashier → 得 selectedSignature + seq
CashierQueryRequest cashierReq = buildCashierQueryRequest(seller, sqbAcquiringSn, sqbOrderSignature); CashierQueryData cashierData = goblinShouQianBaService.queryCashier(sqbAcquiringSn, sqbAcquiringSign, userId);
CashierQueryData cashierData = goblinShouQianBaService.queryCashier(cashierReq);
if (cashierData == null) { if (cashierData == null) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity); goblinRedisUtils.incrSkuStock(null, skuId, quantity);
return ResponseDto.failure("查询收银台失败"); return ResponseDto.failure("查询收银台失败");
} }
String selectedSignature = cashierData.getSelectedSignature();
String seq = cashierData.getSeq();
log.info("[收钱吧下单] queryCashier 成功,selectedSignature={}, seq={}", selectedSignature, seq);
// Step 4.4: createWechatPrepayOrder → 得 paymentVoucher String selectedSignature = cashierData.getSelectedSignature(); // 支付工具签名
CreateWechatPrepayOrderRequest prepayReq = buildWechatPrepayRequest( String seq = cashierData.getSeq(); // 响应唯一序列号
seller, sqbAcquiringSn, sqbOrderSignature, selectedSignature, seq, cashierData); log.info("[收钱吧下单] 查询收银台成功,selectedSignature={}, seq={}", selectedSignature, seq);
CreateWechatPrepayOrderData prepayData = goblinShouQianBaService.createWechatPrepayOrder(prepayReq);
CreateWechatPrepayOrderData prepayData = goblinShouQianBaService.createWechatPrepayOrder(sqbAcquiringSn,
sqbAcquiringSign,
userId,
"",
"",
cashierData.getPayTools().get(0),
new HashMap<String, Object>(),
selectedSignature,
seq
);
if (prepayData == null || prepayData.getPaymentVoucher() == null) { if (prepayData == null || prepayData.getPaymentVoucher() == null) {
goblinRedisUtils.incrSkuStock(null, skuId, quantity); goblinRedisUtils.incrSkuStock(null, skuId, quantity);
return ResponseDto.failure("创建微信预支付失败"); return ResponseDto.failure("创建微信预支付失败");
} }
CreateWechatPrepayOrderData.PaymentVoucher pv = prepayData.getPaymentVoucher(); CreateWechatPrepayOrderData.PaymentVoucher pv = prepayData.getPaymentVoucher();
log.info("[收钱吧下单] createWechatPrepayOrder 成功,timeStamp={}", pv.getTimeStamp()); log.info("[收钱吧下单] 创建微信预支付订单成功,timeStamp={}", pv.getTimeStamp());
String orderId = IDGenerator.nextSnowId(); String orderId = IDGenerator.nextSnowId();
String masterOrderCode = IDGenerator.nextTimeId(); String masterOrderCode = IDGenerator.nextTimeId();
...@@ -271,7 +284,7 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -271,7 +284,7 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
}); });
// 借用 Base 模块中 ConsumerGoblinOrderCPRdsReceiver 这个队列消费类进行写库 // 借用 Base 模块中 ConsumerGoblinOrderCPRdsReceiver 这个队列消费类进行写库
if(queueUtils != null) { if (queueUtils != null) {
queueUtils.sendMsgByRedis(com.liquidnet.service.base.constant.MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(), queueUtils.sendMsgByRedis(com.liquidnet.service.base.constant.MQConst.GoblinQueue.GOBLIN_ORDER_CREATE_PAY.getKey(),
com.liquidnet.service.base.SqlMapping.gets(sqls, sqlDataSku, sqlDataOrder)); com.liquidnet.service.base.SqlMapping.gets(sqls, sqlDataSku, sqlDataOrder));
// 待支付超时回调等处理也可放入原有股票队列逻辑,暂省略 // 待支付超时回调等处理也可放入原有股票队列逻辑,暂省略
...@@ -302,6 +315,27 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -302,6 +315,27 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
} }
} }
/**
* 构建收钱吧-结算明细条目
* @param skuVo
* @param quantity
* @param sqbSkuId
* @param sqbSpuId
* @return
*/
private SettlementCreateRequest.CheckoutItem buildSqbCheckOutItem(GoblinGoodsSkuInfoVo skuVo, Integer quantity, String sqbSkuId, String sqbSpuId) {
// 获取商品与收钱吧商品对应的关联的skuId、spuId
SettlementCreateRequest.CheckoutItem checkoutItem = new SettlementCreateRequest.CheckoutItem();
checkoutItem.setSpuId(sqbSpuId);
checkoutItem.setSkuId(sqbSkuId);
checkoutItem.setPrice(sqbConvertUtils.yuanToFen(skuVo.getPrice()));
checkoutItem.setQuantity(String.valueOf(quantity));
checkoutItem.setType((byte) 0);
checkoutItem.setTitle(skuVo.getName());
checkoutItem.setImage(skuVo.getSkuPic());
return checkoutItem;
}
// ================================ 查询支付状态 ================================ // ================================ 查询支付状态 ================================
@Override @Override
...@@ -371,10 +405,6 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -371,10 +405,6 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
CouponQueryRequest req = new CouponQueryRequest(); CouponQueryRequest req = new CouponQueryRequest();
req.setAppid(shouqianbaProperties.getAppId()); req.setAppid(shouqianbaProperties.getAppId());
req.setSeller(buildSeller()); req.setSeller(buildSeller());
CouponQueryRequest.OrderInfo orderInfo = new CouponQueryRequest.OrderInfo();
orderInfo.setSn(orderVo.getSqbOrderSn());
orderInfo.setSignature(orderVo.getSqbOrderSignature());
req.setOrderID(orderInfo);
CouponQueryData couponData = goblinShouQianBaService.queryCoupon(req); CouponQueryData couponData = goblinShouQianBaService.queryCoupon(req);
if (couponData == null) return ResponseDto.failure("获取券码失败"); if (couponData == null) return ResponseDto.failure("获取券码失败");
...@@ -424,11 +454,6 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService { ...@@ -424,11 +454,6 @@ public class GoblinSqbOrderServiceImpl implements IGoblinSqbOrderService {
refundReq.setRequestSource("EXTERN"); refundReq.setRequestSource("EXTERN");
refundReq.setRequestId(IDGenerator.nextSnowId()); refundReq.setRequestId(IDGenerator.nextSnowId());
CouponRefundRequest.OrderInfo orderInfo = new CouponRefundRequest.OrderInfo();
orderInfo.setSn(orderVo.getSqbOrderSn());
orderInfo.setSignature(orderVo.getSqbOrderSignature());
refundReq.setOrderID(orderInfo);
CouponRefundRequest.RefundInfo refundInfo = new CouponRefundRequest.RefundInfo(); CouponRefundRequest.RefundInfo refundInfo = new CouponRefundRequest.RefundInfo();
// amount 字段待接入实际金额(GoblinSqbOrderVo 需补充 amount) // amount 字段待接入实际金额(GoblinSqbOrderVo 需补充 amount)
refundInfo.setApplyAmount(0L); // TODO: 替换为实际订单金额 refundInfo.setApplyAmount(0L); // TODO: 替换为实际订单金额
......
package com.liquidnet.service.goblin.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
/**
* 收钱吧参数转换工具类
* 1. 金额转换:元(BigDecimal) -> 分(Long)
*/
@Slf4j
@Component
public class GoblinSqbConvertUtils {
/**
* 元转分 (BigDecimal -> Long)
* @param amount 元
* @return 分
*/
public Long yuanToFen(BigDecimal amount) {
if (amount == null) {
return 0L;
}
// 乘以 100 并设置四舍五入,确保转换后的精度
return amount.multiply(new BigDecimal("100"))
.setScale(0, RoundingMode.HALF_UP)
.longValue();
}
/**
* 根据正在的skuId、spuId获取收钱吧信息
* @param skuId
* @param spuId
* @return
*/
public Map<String, String> getSqbInfoByzhengzaiSkuIdAndSpuId(String skuId, String spuId) {
return new HashMap<>();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment