Просмотр исходного кода

add alipay

release_toaliyun_real
xiaohanzi 5 лет назад
Родитель
Сommit
f7d339bec9
17 измененных файлов: 846 добавлений и 68 удалений
  1. +0
    -1
      mallinkAdmin/src/main/java/com/iformall/controller/sys/UploadController.java
  2. +40
    -0
      mallinkAdmin/src/main/resources/db/migration/V202104150001__alipay.sql
  3. +82
    -0
      mallinkCApi/src/main/java/com/iformall/controller/AliPayController.java
  4. +75
    -0
      mallinkCallback/src/main/java/com/iformall/controller/callback/AliPayController.java
  5. +90
    -0
      mallinkSchedule/src/main/java/com/iformall/schedule/CashOutSchedule.java
  6. +6
    -1
      mallinkService/pom.xml
  7. +8
    -0
      mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java
  8. +4
    -3
      mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java
  9. +59
    -59
      mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java
  10. +159
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/AliPayUtil.java
  11. +166
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/api/AliPayApi.java
  12. +21
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelColumn.java
  13. +28
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelFieldRule.java
  14. +52
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelRequest.java
  15. +35
    -0
      mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelStyle.java
  16. +6
    -4
      mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java
  17. +15
    -0
      mallinkService/src/main/resources/mapper/WxCashOutMapper.xml

+ 0
- 1
mallinkAdmin/src/main/java/com/iformall/controller/sys/UploadController.java Просмотреть файл

@@ -52,7 +52,6 @@ public class UploadController extends BaseController {
if (dot >= 0) { if (dot >= 0) {
fileFormat = multiReq.getOriginalFilename().substring(dot, multiReq.getOriginalFilename().length()); fileFormat = multiReq.getOriginalFilename().substring(dot, multiReq.getOriginalFilename().length());
} }

ResultData data = aliyunOSS.uploadFile(tenantEntity.getTenantId(), fileFormat, multiReq.getInputStream()); ResultData data = aliyunOSS.uploadFile(tenantEntity.getTenantId(), fileFormat, multiReq.getInputStream());
return data; return data;




+ 40
- 0
mallinkAdmin/src/main/resources/db/migration/V202104150001__alipay.sql Просмотреть файл

@@ -0,0 +1,40 @@
ALTER TABLE `wx_mall`
ADD COLUMN `cash_out_support` INT(1) COMMENT '是否支持商户提现(不开启分账才有效) 0-不支持 1-支持' AFTER `live_support`;

UPDATE wx_mall SET cash_out_support = 0 ;

ALTER TABLE wx_merchant ADD COLUMN `cash_out_number` INT(11) DEFAULT 0 COMMENT '商户待提现金额(分)' ;

###页面添加工作流,提现审批,22, 初始化里面也加上

CREATE TABLE `wx_cash_out` (
`id` bigint(20) NOT NULL,
`total_fee` int(11) NOT NULL COMMENT '总金额(分)',
`status` tinyint(1) NOT NULL COMMENT '状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝',
`create_date` datetime DEFAULT NULL COMMENT '创建时间',
`update_date` datetime DEFAULT NULL COMMENT '更新时间',
`audio_remark` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '审核备注',
`tenant_id` varchar(5) COLLATE utf8mb4_unicode_ci NOT NULL,
`parent_tenant_id` varchar(5) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`b_user_id` bigint(20) NOT NULL COMMENT 'merchant_b_user编号',
`wx_b_open_id` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'b端微信openId',
`audio_date` datetime DEFAULT NULL COMMENT '审批时间',
`merchant_id` bigint(20) NOT NULL COMMENT '提现商户',
`merchant_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商户名称',
`recive_open_id` varchar(60) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '接收的openId',
`recive_nick_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '昵称',
`wx_pay_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付渠道支付编号',
`wx_pay_time` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '支付渠道支付时间',
`fail_remark` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '失败原因',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `wx_mall`
ADD COLUMN `cash_out_limit` INT(3) COMMENT '商户提现每日次数限制' AFTER `cash_out_support`;

UPDATE wx_mall SET cash_out_limit = 10 ;

ALTER TABLE wx_merchant ADD COLUMN `cash_out_count` INT(3) DEFAULT 0 COMMENT '当日提现次数' ;




+ 82
- 0
mallinkCApi/src/main/java/com/iformall/controller/AliPayController.java Просмотреть файл

@@ -0,0 +1,82 @@
package com.iformall.controller;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;
import cn.binarywang.wx.miniapp.bean.WxMaUserInfo;
import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.iformall.annotation.AuthIgnore;
import com.iformall.annotation.TenantIgnore;
import com.iformall.common.ErrorCode;
import com.iformall.common.Result;
import com.iformall.common.ResultData;
import com.iformall.domain.po.*;
import com.iformall.domain.po.base.TenantEntity;
import com.iformall.domain.vo.WxCUserVo;
import com.iformall.domain.vo.WxLevelMerchantCVo;
import com.iformall.enums.*;
import com.iformall.service.*;
import com.iformall.service.wechat.FmOpenService;
import com.iformall.utils.Constant;
import com.iformall.utils.IPUtil;
import com.iformall.utils.RedisCacheUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/alipay")
@Api(description = "支付宝相关接口,包括支付宝用户端操作回调接口")
public class AliPayController extends BaseController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private WxCUserService wxCUserService;




/**
* 支付宝客户端回调接口
* 1. 商圈授权给isv后的回调。 isv账号登陆开放平台(https://openhome.alipay.com),选择平台应用,菜单“商家授权应用-发起授权”,复制链接或二维码给商圈支付宝账号扫。
* 2.用户在同意领取支付宝会员卡之后的回调。
* @param response
* @param request
* @throws IOException
*/
@AuthIgnore
@RequestMapping(value = "/callback")
@ResponseBody
public void signature(HttpServletResponse response, HttpServletRequest request) throws IOException {

String source = request.getParameter("source");
//商家授权给ISV后的回调
if ("alipay_app_auth".equals(source)) {
PrintWriter out = response.getWriter();
out.print("授权成功!");
out.close();
}
}
}

+ 75
- 0
mallinkCallback/src/main/java/com/iformall/controller/callback/AliPayController.java Просмотреть файл

@@ -0,0 +1,75 @@
package com.iformall.controller.callback;

import com.alibaba.fastjson.JSON;
import com.iformall.controller.base.BaseController;
import com.iformall.domain.po.msg.FmInsideNotifyPaySuccessMsg;
import com.iformall.domain.po.msg.FmInsideNotifyRefundSuccessMsg;
import com.iformall.enums.*;
import com.iformall.exception.BizMessageException;
import com.iformall.exception.MallinkException;
import com.iformall.mq.MqBaseProducer;
import com.iformall.pay.WxPayment;
import com.iformall.service.WxPayOrderService;
import com.iformall.service.WxRefundOrderService;
import com.iformall.service.WxSubsidyService;
import com.iformall.utils.XmlUtil;
import org.apache.commons.io.IOUtils;
import org.jdom2.JDOMException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

@RestController
@RequestMapping("/alipay/notify")
public class AliPayController extends BaseController {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private WxPayOrderService wxPayOrderService;

@Autowired
private WxSubsidyService wxSubsidyService;

@Autowired
private MqBaseProducer mqBaseProducer;
/**
*ISV_GATEWAY_URL?charset=GBK&biz_content=
{
"trade_no":"144534352534536","out_trade_no":"123456789","total_amount":"25.00","buyer_pay_amount":"16.50","receipt_amount":"20.00","gmt_payment":"2018-05-05 14:24:12","buyer_id":"2088102121935776","mall_cell_type":"SMID","mall_cell_id":"12345","mall_pid":"2088102121935788","mall_id":"1234567","mall_name":"测试商圈名称","mall_store_id":"001","notify_receive_pid":"2088102121935799"
}
&msg_method=alipay.business.mall.trade.success&utc_timestamp=1516797622752&version=1.1&sign_type=RSA2&notify_id=d275fec564e62af6bedbcee73f3f05fi5x&app_id=2013121700999429&sign=I+Y/lvqYUEEc10EPdpntRhFIQ==
ISV_GATEWAY_URL?charset=GBK&biz_content=
{
"trade_no":"2019082222001464620500962620","out_trade_no":"out202002240001","out_request_no":"outrequest0001","refund_fee":"20.00","gmt_refund_pay":"2020-01-05 14:24:12","buyer_id":"2088102121935776","mall_cell_type":"SMID","mall_cell_id":"12345","mall_pid":"2088102121935788","mall_id":"1234567","mall_store_id":"001","mall_name":"测试商圈名称","notify_receive_pid":"2088102121935799"
}
&msg_method=alipay.business.mall.trade.refunded&utc_timestamp=1516797622752&version=1.1&sign_type=RSA2&notify_id=d275fec564e62af6bedbcee73f3f05fi5x&app_id=2013121700999429&sign=I+Y/lvqYUEEc10EPdpntRhFIQ==
* @throws Exception 可能产生的任何异常
*/
@PostMapping(value = "/")
public void __Notify(@RequestBody Map<String, Object> paramMap) throws Exception {
logger.info(">>>>>>>>alipay callback"+paramMap);
}
}

+ 90
- 0
mallinkSchedule/src/main/java/com/iformall/schedule/CashOutSchedule.java Просмотреть файл

@@ -0,0 +1,90 @@
package com.iformall.schedule;

import com.iformall.domain.po.WxCashOut;
import com.iformall.domain.po.WxCouponChannel;
import com.iformall.domain.po.WxCouponOrder;
import com.iformall.domain.po.WxGame;
import com.iformall.domain.po.WxMall;
import com.iformall.domain.po.WxMerchant;
import com.iformall.enums.EnumCashOutStatus;
import com.iformall.enums.EnumCouponChannelStatus;
import com.iformall.enums.EnumCouponChannelType;
import com.iformall.enums.EnumGameStatus;
import com.iformall.mapper.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

@Component
public class CashOutSchedule {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
private WxCashOutMapper wxCashOutMapper;
@Autowired
private WxMallMapper wxMallMapper;
@Autowired
private WxMerchantMapper wxMerchantMapper;

private List<WxMall> getMalls() {
WxMall wxMall = new WxMall();
return wxMallMapper.findList(wxMall);
}

@Scheduled(cron = "0/5 * * * * ?") // 执行一次
public void cashOutFailSchedule() {
List<WxMall> malls = getMalls();
for (WxMall mall : malls) {
try {
WxCashOut cashOut = new WxCashOut();
cashOut.updateTenantInfo(mall);
List<Integer> statausLis = new ArrayList<Integer>();
statausLis.add(EnumCashOutStatus.FAIL.getCode());
statausLis.add(EnumCashOutStatus.DOING.getCode());
cashOut.setStatusList(statausLis);
Date curDate = new Date();
//时间在1天以上的
Calendar startA = Calendar.getInstance();
startA.setTime(curDate);
startA.add(Calendar.DAY_OF_YEAR, -1);
cashOut.setCreateDateEnd(startA.getTime());
List<WxCashOut> cashoutlist = wxCashOutMapper.findList(cashOut);
if (null != cashoutlist && cashoutlist.size() > 0 ) {
for (WxCashOut casho : cashoutlist) {
cashOutBack(casho);
}
}
}catch(Exception e) {
logger.error("couponExpiringSchedule error.mall:"+mall.getTenantId(),e);
}
}
}

@Transactional(propagation = Propagation.REQUIRED, readOnly = false, rollbackFor = {Exception.class})
public void cashOutBack(WxCashOut cashout) {
//状态更新
cashout.setStatus(EnumCashOutStatus.BACK.getCode());
cashout.setUpdateDate(new Date());
wxCashOutMapper.updateStatus(cashout);
//余额回滚
WxMerchant merchant = wxMerchantMapper.selectById(cashout.getMerchantId());
merchant.setCashOutNumber(cashout.getTotalFee());
wxMerchantMapper.increaseCash(merchant);
}

}

+ 6
- 1
mallinkService/pom.xml Просмотреть файл

@@ -34,7 +34,12 @@
<groupId>com.iformall</groupId> <groupId>com.iformall</groupId>
<artifactId>mybatis-multi-tenancy</artifactId> <artifactId>mybatis-multi-tenancy</artifactId>
<version>1.0</version> <version>1.0</version>
</dependency>
</dependency>
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-easysdk</artifactId>
<version>2.2.0</version>
</dependency>
</dependencies> </dependencies>


</project> </project>

+ 8
- 0
mallinkService/src/main/java/com/iformall/domain/po/WxCashOut.java Просмотреть файл

@@ -1,5 +1,6 @@
package com.iformall.domain.po; package com.iformall.domain.po;


import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.iformall.domain.po.base.TenantEntity; import com.iformall.domain.po.base.TenantEntity;
import lombok.Data; import lombok.Data;
@@ -7,6 +8,7 @@ import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;


import java.util.Date; import java.util.Date;
import java.util.List;


@TableName(value = "wx_cash_out") @TableName(value = "wx_cash_out")
@Data @Data
@@ -20,8 +22,14 @@ public class WxCashOut extends TenantEntity {
private Integer totalFee; private Integer totalFee;
@io.swagger.annotations.ApiModelProperty(value = "状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝", name = "status") @io.swagger.annotations.ApiModelProperty(value = "状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝", name = "status")
private Integer status; private Integer status;
@TableField(exist = false)
private List<Integer> statusList;
@io.swagger.annotations.ApiModelProperty(value = "创建时间", name = "createDate") @io.swagger.annotations.ApiModelProperty(value = "创建时间", name = "createDate")
private Date createDate; private Date createDate;
@TableField(exist = false)
private Date createDateBegin;
@TableField(exist = false)
private Date createDateEnd;
@io.swagger.annotations.ApiModelProperty(value = "更新时间", name = "updateDate") @io.swagger.annotations.ApiModelProperty(value = "更新时间", name = "updateDate")
private Date updateDate; private Date updateDate;
@io.swagger.annotations.ApiModelProperty(value = "审核备注", name = "audioRemark") @io.swagger.annotations.ApiModelProperty(value = "审核备注", name = "audioRemark")


+ 4
- 3
mallinkService/src/main/java/com/iformall/enums/EnumCashOutStatus.java Просмотреть файл

@@ -5,12 +5,13 @@ package com.iformall.enums;
*/ */
public enum EnumCashOutStatus { public enum EnumCashOutStatus {
//状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝 //状态 0-提现中,1-提现成功,2-提现失败,3-提现拒绝
DOING(0, "提现"),
DOING(0, "发起提现"),
SUCCESS(1, "提现成功"), SUCCESS(1, "提现成功"),
FAIL(2, "提现失败"),
FAIL(2, "提现失败,待退回余额"),
REJECT(3, "提现拒绝"), REJECT(3, "提现拒绝"),
AUDIOING(4, "审批中"), AUDIOING(4, "审批中"),
WAITING(5, "待到账")
WAITING(5, "审批通过,待到账"),
BACK(6,"提现失败,已回退余额");
; ;


public static EnumCashOutStatus getEnum(Integer code) { public static EnumCashOutStatus getEnum(Integer code) {


+ 59
- 59
mallinkService/src/main/java/com/iformall/service/impl/WxCashOutServiceImpl.java Просмотреть файл

@@ -257,65 +257,65 @@ public class WxCashOutServiceImpl implements WxCashOutService {
public ResultData reCashout(WxMerchantBUser buser, WxCashOut record, EnumPayWay payWay) { public ResultData reCashout(WxMerchantBUser buser, WxCashOut record, EnumPayWay payWay) {
//不能重复提交操作 //不能重复提交操作
long time = System.currentTimeMillis() + RedisLock.LONG_TIMEOUT;
String timeStr = String.valueOf(time);
boolean cashsetlock = redisLock.lock("merchantLockReCashSet_"+record.getId(), timeStr);
if (cashsetlock) {
try {
WxCashOut cashout = this.getById(record.getId(), buser.getTenantId());
if (null == cashout ) {
return new ResultData(Result.ERROR,"该提现记录不存在");
}
if( EnumCashOutStatus.FAIL.getCode().intValue() != record.getStatus().intValue()) {
return new ResultData(Result.ERROR,"提现失败状态才能重新提现。");
}
//只有商户管理员才能进行此操作
WxMerchant merchant = wxMerchantMapper.selectById(buser.getMerchantId());
if (!(buser.getPhone().equals(merchant.getLinkPhone()))) {
return new ResultData(Result.ERROR,"当前账号非商户管理员,不能进行此操作。");
}
WxMall mall = wxMallMapper.getByTenantId(buser.getTenantId());
if(!hasCashCount(mall,merchant)) {
return new ResultData(Result.ERROR,"当日提现次数已达限额。");
}
WxAppinfo appInfo = wxAppinfoService.getCAppInfo(buser, payWay);
if (null == appInfo) {
return new ResultData(Result.ERROR,"系统错误,appInfo未查询到。");
}
WxPayAccount payAccount = wxPayAccountService.getByTenantId(buser.getTenantId());
if (null == payAccount) {
return new ResultData(Result.ERROR,"系统错误,payAccount未查询到。");
}
//查询是否已经真实的转过了
CashOutAdapterResult qresult = payServiceFactory.getCashOutAdapterService(payWay.getCode()).queryCashOut(appInfo, payAccount, cashout);
if (qresult.isSuccess()) {
cashout.setStatus(EnumCashOutStatus.SUCCESS.getCode());
wxCashOutMapper.updateStatus(cashout);
}else {
CashOutAdapterResult result = payServiceFactory.getCashOutAdapterService(payWay.getCode()).cashOut(appInfo, payAccount, cashout);
cashout.setUpdateDate(new Date());
if (result.isSuccess()) {
cashout.setStatus(EnumCashOutStatus.SUCCESS.getCode());
}else {
cashout.setStatus(EnumCashOutStatus.FAIL.getCode());
cashout.setFailRemark(result.getMsg());
}
wxCashOutMapper.updateById(cashout);
}
//更新次数
wxMerchantMapper.increaseCashCount(merchant);
redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
}catch(Exception e) {
redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
logger.error("update cashout fail.",e);
}finally {
redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
}
}
// long time = System.currentTimeMillis() + RedisLock.LONG_TIMEOUT;
// String timeStr = String.valueOf(time);
// boolean cashsetlock = redisLock.lock("merchantLockReCashSet_"+record.getId(), timeStr);
// if (cashsetlock) {
// try {
// WxCashOut cashout = this.getById(record.getId(), buser.getTenantId());
// if (null == cashout ) {
// return new ResultData(Result.ERROR,"该提现记录不存在");
// }
//
// if( EnumCashOutStatus.FAIL.getCode().intValue() != record.getStatus().intValue()) {
// return new ResultData(Result.ERROR,"提现失败状态才能重新提现。");
// }
//
// //只有商户管理员才能进行此操作
// WxMerchant merchant = wxMerchantMapper.selectById(buser.getMerchantId());
// if (!(buser.getPhone().equals(merchant.getLinkPhone()))) {
// return new ResultData(Result.ERROR,"当前账号非商户管理员,不能进行此操作。");
// }
//
// WxMall mall = wxMallMapper.getByTenantId(buser.getTenantId());
// if(!hasCashCount(mall,merchant)) {
// return new ResultData(Result.ERROR,"当日提现次数已达限额。");
// }
//
// WxAppinfo appInfo = wxAppinfoService.getCAppInfo(buser, payWay);
// if (null == appInfo) {
// return new ResultData(Result.ERROR,"系统错误,appInfo未查询到。");
// }
// WxPayAccount payAccount = wxPayAccountService.getByTenantId(buser.getTenantId());
// if (null == payAccount) {
// return new ResultData(Result.ERROR,"系统错误,payAccount未查询到。");
// }
// //查询是否已经真实的转过了
// CashOutAdapterResult qresult = payServiceFactory.getCashOutAdapterService(payWay.getCode()).queryCashOut(appInfo, payAccount, cashout);
// if (qresult.isSuccess()) {
// cashout.setStatus(EnumCashOutStatus.SUCCESS.getCode());
// wxCashOutMapper.updateStatus(cashout);
// }else {
// CashOutAdapterResult result = payServiceFactory.getCashOutAdapterService(payWay.getCode()).cashOut(appInfo, payAccount, cashout);
// cashout.setUpdateDate(new Date());
// if (result.isSuccess()) {
// cashout.setStatus(EnumCashOutStatus.SUCCESS.getCode());
// }else {
// cashout.setStatus(EnumCashOutStatus.FAIL.getCode());
// cashout.setFailRemark(result.getMsg());
// }
// wxCashOutMapper.updateById(cashout);
// }
// //更新次数
// wxMerchantMapper.increaseCashCount(merchant);
// redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
// }catch(Exception e) {
// redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
// logger.error("update cashout fail.",e);
// }finally {
// redisLock.unlock("merchantLockReCashSet_"+record.getId(), timeStr);
// }
// }
return new ResultData(); return new ResultData();
} }
} }

+ 159
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/AliPayUtil.java Просмотреть файл

@@ -0,0 +1,159 @@
package com.iformall.service.pay.alipay;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.easysdk.base.oauth.models.AlipaySystemOauthTokenResponse;
import com.alipay.easysdk.util.generic.models.AlipayOpenApiGenericResponse;
import com.iformall.service.pay.alipay.api.AliPayApi;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelColumn;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelFieldRule;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelRequest;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelStyle;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class AliPayUtil {

//查询app_auth_token
public static String getAppAuthToken(String appAuthCode) {
try {
AlipaySystemOauthTokenResponse response = AliPayApi.getAppAuthToken(appAuthCode);
return response.getAccessToken();
} catch (Exception e) {
log.error("alipay getAppAuthToken error. ",e);
}
return null;
}
private static JSONObject getGenericResponse(String result,String key) {
if (StringUtils.isBlank(result)) {
log.error("alipay apiresponse ["+key+"] error. no result."+result);
return null;
}
JSONObject object = JSON.parseObject(result);
if (null != object) {
JSONObject resultObject = object.getJSONObject(key);
if (null != resultObject ) {
String code = resultObject.getString("code");
if (("10000".equals(code))) {
return resultObject;
}
}
}
log.error("alipay apiresponse ["+key+"] error. "+result);
return null;
}
//门店照片上传
public static String merchantImageUpload(String appAuthToken,String imageName,byte[] file) {
try {
AlipayOpenApiGenericResponse response = AliPayApi.uploadMerchantImage(appAuthToken,imageName, file);
JSONObject result = getGenericResponse(response.getHttpBody(),"alipay_offline_material_image_upload_response");
if (null != result) {
return result.getString("image_id");
}
} catch (Exception e) {
log.error("alipay merchantImageUpload error. ",e);
}
return null;
}
//创建商圈会员卡
public static String createSmartDistrictMemberCardModel(String appAuthToken,String cardName,String logoId,String backImageId) {
AlipayMemberCardModelRequest request = new AlipayMemberCardModelRequest();
AlipayMemberCardModelStyle style = new AlipayMemberCardModelStyle();
style.setCard_show_name(cardName);
style.setLogo_id(logoId);
style.setBackground_id(backImageId);
request.setTemplate_style_info(style);
List<AlipayMemberCardModelColumn> columnList = new ArrayList<AlipayMemberCardModelColumn>();
AlipayMemberCardModelColumn column0 = new AlipayMemberCardModelColumn();
column0.setCode("TELEPHONE");
column0.setTitle("联系电话");
columnList.add(column0);
AlipayMemberCardModelColumn column1 = new AlipayMemberCardModelColumn();
column1.setCode("BENEFIT_INFO");
column1.setTitle("会员专享");
columnList.add(column1);
request.setColumn_info_list(columnList);
List<AlipayMemberCardModelFieldRule> ruleList = new ArrayList<AlipayMemberCardModelFieldRule>();
AlipayMemberCardModelFieldRule rule0 = new AlipayMemberCardModelFieldRule();
rule0.setField_name("Balance");
rule0.setRule_name("ASSIGN_FROM_REQUEST");
rule0.setRule_value("Balance");
ruleList.add(rule0);
try {
AlipayOpenApiGenericResponse response = AliPayApi.createMemberCardModel(appAuthToken, request);
JSONObject result = getGenericResponse(response.getHttpBody(),"alipay_marketing_card_template_create_response");
if (null != result) {
return result.getString("template_id");
}
} catch (Exception e) {
log.error("alipay createSmartDistrictMemberCardModel error. ",e);
}
return null;
}
//创建商圈会员卡表单配置
public static boolean setSmartDistrictMemberCardModelConfig(String appAuthToken,String templateId) {
try {
AlipayOpenApiGenericResponse response = AliPayApi.setMemberCardModelConfig(appAuthToken, templateId,
new String[] {"OPEN_FORM_FIELD_MOBILE","OPEN_FORM_FIELD_NAME"},new String[] {"OPEN_FORM_FIELD_GENDER"});
JSONObject result = getGenericResponse(response.getHttpBody(),"alipay_marketing_card_formtemplate_set_response");
if (null != result) {
return true;
}
} catch (Exception e) {
log.error("alipay setSmartDistrictMemberCardModelConfig error. ",e);
}
return false;
}
//获取商圈会员卡领卡投放链接
public static String getSmartDistrictMemberCardUrl(String appAuthToken,String templateId,String callback,String param) {
try {
AlipayOpenApiGenericResponse response = AliPayApi.getMemberCardUrl(appAuthToken, templateId, param, callback);
JSONObject result = getGenericResponse(response.getHttpBody(),"alipay_marketing_card_activateurl_apply_response");
if (null != result) {
return result.getString("apply_card_url");
}
} catch (Exception e) {
log.error("alipay getSmartDistrictMemberCardUrl error. ",e);
}
return null;
}
//商圈智能积分授权(算法授权)URL
public static String getH5SmartDistrictMallVipPointsUrl(String appId,String callback,String param) {
try {
return "https://openauth.alipay.com/oauth2/publicAppAuthorize.htm?app_id=APPID&scope=mall_vip_points&redirect_uri="+URLEncoder.encode(callback,"utf-8")+"&state="+param;
} catch (UnsupportedEncodingException e) {
log.error("alipay getH5MallVipPointsUrl error. ",e);
}
return null;
}
//商圈消息订阅
public static boolean smartDistrictTopicSubscribe(String appAuthToken) {
try {
AlipayOpenApiGenericResponse response = AliPayApi.topicSubscribe(appAuthToken, "app_auth", "alipay.open.auth.appauth.cancelled", "HTTP", "BIZ_TAG");
JSONObject result = getGenericResponse(response.getHttpBody(),"alipay_open_app_message_topic_subscribe_response");
if (null != result) {
return true;
}
} catch (Exception e) {
log.error("alipay smartDistrictTopicSubscribe error. ",e);
}
return false;
}
}

+ 166
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/api/AliPayApi.java Просмотреть файл

@@ -0,0 +1,166 @@
package com.iformall.service.pay.alipay.api;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;

import com.alibaba.fastjson.JSON;
import com.alipay.easysdk.base.image.models.AlipayOfflineMaterialImageUploadResponse;
import com.alipay.easysdk.base.oauth.models.AlipaySystemOauthTokenResponse;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.util.generic.models.AlipayOpenApiGenericResponse;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelColumn;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelFieldRule;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelRequest;
import com.iformall.service.pay.alipay.api.entity.memberCardModel.AlipayMemberCardModelStyle;

/**
* SDK说明 https://opendocs.alipay.com/open/54/00y8k9
* 综合体支付积分https://opendocs.alipay.com/open/01lsmc?scene=SC00001296
* @author alascor
*/
public class AliPayApi {

/**
* https://opendocs.alipay.com/apis/api_9/alipay.open.auth.token.app
* 换取应用授权令牌
* @throws Exception
*/
public static AlipaySystemOauthTokenResponse getAppAuthToken(String appAuthCode) throws Exception {
AlipaySystemOauthTokenResponse response = Factory.Base.OAuth().getToken(appAuthCode);
return response;
}
/**
* https://opendocs.alipay.com/apis/api_3/alipay.offline.material.image.upload
* 上传门店照片和视频接口
* @throws Exception
*/
public static AlipayOfflineMaterialImageUploadResponse uploadMerchantImage(String appAuthToken,String imageName,String localImagePath) throws Exception {
AlipayOfflineMaterialImageUploadResponse response = Factory.Base.Image().agent(appAuthToken).upload(imageName, localImagePath);
return response;
}
public static AlipayOpenApiGenericResponse uploadMerchantImage(String appAuthToken,String imageName,byte[] file) throws Exception {
//设置系统参数(OpenAPI中非biz_content里的参数)
Map<String, String> textParams = new HashMap<String, String>();
textParams.put("app_auth_token", appAuthToken);
//设置业务参数(OpenAPI中biz_content里的参数)
Map<String, Object> bizParams = new HashMap<String, Object>();
bizParams.put("image_type","jpg");
bizParams.put("image_name",imageName);
bizParams.put("image_content",file);
AlipayOpenApiGenericResponse response = Factory.Util.Generic().agent(appAuthToken)
.execute("alipay.offline.material.image.upload", textParams, bizParams);
return response;
}
/**
* https://opendocs.alipay.com/apis/api_5/alipay.marketing.card.template.create
* 会员卡模板创建
* @throws Exception
*/
public static AlipayOpenApiGenericResponse createMemberCardModel(String appAuthToken,AlipayMemberCardModelRequest modelRequest) throws Exception {
//设置系统参数(OpenAPI中非biz_content里的参数)
Map<String, String> textParams = new HashMap<String, String>();
textParams.put("app_auth_token", appAuthToken);

//设置业务参数(OpenAPI中biz_content里的参数)
Map<String, Object> bizParams = new HashMap<String, Object>();
bizParams.put("request_id", UUID.randomUUID().toString());
bizParams.put("card_type", modelRequest.getCard_type());
bizParams.put("biz_no_suffix_len",modelRequest.getBiz_no_suffix_len());
bizParams.put("write_off_type", modelRequest.getWrite_off_type());
AlipayMemberCardModelStyle style = modelRequest.getTemplate_style_info();
bizParams.put("template_style_info", style);
List<AlipayMemberCardModelColumn> columnList = modelRequest.getColumn_info_list();
bizParams.put("column_info_list", columnList);
List<AlipayMemberCardModelFieldRule> ruleList = modelRequest.getField_rule_list();
bizParams.put("field_rule_list", ruleList);

AlipayOpenApiGenericResponse response = Factory.Util.Generic().execute(
"alipay.marketing.card.template.create", textParams, bizParams);
return response;
}
/**
* https://opendocs.alipay.com/apis/api_5/alipay.marketing.card.formtemplate.set
* 会员卡开卡表单模板配置
* @throws Exception
*/
public static AlipayOpenApiGenericResponse setMemberCardModelConfig(String appAuthToken,String templateId,String[] requiredFields,String[] optionalFields) throws Exception {
//设置系统参数(OpenAPI中非biz_content里的参数)
Map<String, String> textParams = new HashMap<String, String>();
textParams.put("app_auth_token", appAuthToken);

//设置业务参数(OpenAPI中biz_content里的参数)
Map<String, Object> bizParams = new HashMap<String, Object>();
bizParams.put("template_id", templateId);
Map<String, String> extendParams = new HashMap<>();
Map<String,String[]> requireds = new HashMap<String,String[]>();
requireds.put("common_fields", requiredFields);
extendParams.put("required", JSON.toJSONString(requireds));
if (null != optionalFields) {
Map<String,String[]> optional = new HashMap<String,String[]>();
optional.put("common_fields", optionalFields);
extendParams.put("optional", JSON.toJSONString(optional));
}
bizParams.put("fields", extendParams);

AlipayOpenApiGenericResponse response = Factory.Util.Generic().execute(
"alipay.marketing.card.formtemplate.set", textParams, bizParams);
return response;
}
/**
* https://opendocs.alipay.com/apis/api_5/alipay.marketing.card.activateurl.apply
* 获取会员卡领卡投放链接
*/
public static AlipayOpenApiGenericResponse getMemberCardUrl(String appAuthToken,String templateId,String exparam,String callback) throws Exception {
//设置系统参数(OpenAPI中非biz_content里的参数)
Map<String, String> textParams = new HashMap<String, String>();
textParams.put("app_auth_token", appAuthToken);

//设置业务参数(OpenAPI中biz_content里的参数)
Map<String, Object> bizParams = new HashMap<String, Object>();
bizParams.put("template_id", templateId);
bizParams.put("out_string", exparam);
bizParams.put("callback", callback);
AlipayOpenApiGenericResponse response = Factory.Util.Generic().execute(
"alipay.marketing.card.activateurl.apply", textParams, bizParams);
return response;
}
/**
* https://opendocs.alipay.com/apis/api_9/alipay.open.app.message.topic.subscribe
* 订阅消息主题
* @return
* @throws Exception
*/
public static AlipayOpenApiGenericResponse topicSubscribe(String appAuthToken,String authType,String topic,String type,String tag) throws Exception {
//设置系统参数(OpenAPI中非biz_content里的参数)
Map<String, String> textParams = new HashMap<String, String>();
textParams.put("app_auth_token", appAuthToken);

//设置业务参数(OpenAPI中biz_content里的参数)
Map<String, Object> bizParams = new HashMap<String, Object>();
bizParams.put("auth_token", appAuthToken);
bizParams.put("auth_type", authType);
bizParams.put("topic", topic);
bizParams.put("comm_type", type);
if (!StringUtils.isBlank(tag)) {
bizParams.put("tag",tag);
}
AlipayOpenApiGenericResponse response = Factory.Util.Generic().execute(
"alipay.open.app.message.topic.subscribe", textParams, bizParams);
return response;
}
}

+ 21
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelColumn.java Просмотреть файл

@@ -0,0 +1,21 @@
package com.iformall.service.pay.alipay.api.entity.memberCardModel;

import com.aliyun.tea.TeaModel;

public class AlipayMemberCardModelColumn{

private String code;//标准栏位:行为由支付宝统一定,同时已经分配标准Code
private String title;//栏目的标题
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

+ 28
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelFieldRule.java Просмотреть файл

@@ -0,0 +1,28 @@
package com.iformall.service.pay.alipay.api.entity.memberCardModel;

import com.aliyun.tea.TeaModel;

public class AlipayMemberCardModelFieldRule{

private String field_name;//字段名称,现在支持如下几个Key(暂不支持自定义)
private String rule_name;//规则名
private String rule_value;//根据rule_name,采取相应取值策略
public String getField_name() {
return field_name;
}
public void setField_name(String field_name) {
this.field_name = field_name;
}
public String getRule_name() {
return rule_name;
}
public void setRule_name(String rule_name) {
this.rule_name = rule_name;
}
public String getRule_value() {
return rule_value;
}
public void setRule_value(String rule_value) {
this.rule_value = rule_value;
}
}

+ 52
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelRequest.java Просмотреть файл

@@ -0,0 +1,52 @@
package com.iformall.service.pay.alipay.api.entity.memberCardModel;

import java.util.List;

import com.aliyun.tea.NameInMap;
import com.aliyun.tea.Validation;

public class AlipayMemberCardModelRequest{

private String card_type="OUT_MEMBER_CARD";//卡类型。可选类型如下:OUT_MEMBER_CARD:外部权益卡
private String biz_no_suffix_len ="8";//业务卡号后缀的长度,取值范围为[8,32]
private String write_off_type = "qrcode";//卡包详情页面中展现出的卡码
private AlipayMemberCardModelStyle template_style_info;//模板样式信息
private List<AlipayMemberCardModelColumn> column_info_list;//栏位信息
private List<AlipayMemberCardModelFieldRule> field_rule_list;//字段规则列表,会员卡开卡过程中,会员卡信息的生成规则
public String getCard_type() {
return card_type;
}
public void setCard_type(String card_type) {
this.card_type = card_type;
}
public String getBiz_no_suffix_len() {
return biz_no_suffix_len;
}
public void setBiz_no_suffix_len(String biz_no_suffix_len) {
this.biz_no_suffix_len = biz_no_suffix_len;
}
public String getWrite_off_type() {
return write_off_type;
}
public void setWrite_off_type(String write_off_type) {
this.write_off_type = write_off_type;
}
public AlipayMemberCardModelStyle getTemplate_style_info() {
return template_style_info;
}
public void setTemplate_style_info(AlipayMemberCardModelStyle template_style_info) {
this.template_style_info = template_style_info;
}
public List<AlipayMemberCardModelColumn> getColumn_info_list() {
return column_info_list;
}
public void setColumn_info_list(List<AlipayMemberCardModelColumn> column_info_list) {
this.column_info_list = column_info_list;
}
public List<AlipayMemberCardModelFieldRule> getField_rule_list() {
return field_rule_list;
}
public void setField_rule_list(List<AlipayMemberCardModelFieldRule> field_rule_list) {
this.field_rule_list = field_rule_list;
}
}

+ 35
- 0
mallinkService/src/main/java/com/iformall/service/pay/alipay/api/entity/memberCardModel/AlipayMemberCardModelStyle.java Просмотреть файл

@@ -0,0 +1,35 @@
package com.iformall.service.pay.alipay.api.entity.memberCardModel;

import com.aliyun.tea.TeaModel;

public class AlipayMemberCardModelStyle{

private String card_show_name;//钱包端显示名称(字符串长度)
private String logo_id;//logo的图片ID
private String background_id;//背景图片Id
private String bg_color="rgb(55,112,179)";//字体颜色(非背景色),只影响卡详情中部信息区域字体颜色
public String getCard_show_name() {
return card_show_name;
}
public void setCard_show_name(String card_show_name) {
this.card_show_name = card_show_name;
}
public String getLogo_id() {
return logo_id;
}
public void setLogo_id(String logo_id) {
this.logo_id = logo_id;
}
public String getBackground_id() {
return background_id;
}
public void setBackground_id(String background_id) {
this.background_id = background_id;
}
public String getBg_color() {
return bg_color;
}
public void setBg_color(String bg_color) {
this.bg_color = bg_color;
}
}

+ 6
- 4
mallinkService/src/main/java/com/iformall/service/pay/service/cashout/wx/WxCashOutAdapterService.java Просмотреть файл

@@ -187,7 +187,9 @@ public class WxCashOutAdapterService implements CashOutAdapterService{
" }\n" + " }\n" +
"}"); "}");
/**
* https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay.php?chapter=14_2
*/
@Override @Override
public CashOutAdapterResult cashOut(WxAppinfo appInfo, WxPayAccount payAccount, WxCashOut cashOut) { public CashOutAdapterResult cashOut(WxAppinfo appInfo, WxPayAccount payAccount, WxCashOut cashOut) {
WxCashOutP wxCashOutP = generateWxCashOutP(payAccount, appInfo, cashOut); WxCashOutP wxCashOutP = generateWxCashOutP(payAccount, appInfo, cashOut);
@@ -196,7 +198,7 @@ public class WxCashOutAdapterService implements CashOutAdapterService{
signMap = BeanUtils.toStringMap(wxCashOutP); signMap = BeanUtils.toStringMap(wxCashOutP);
} catch (Exception e) { } catch (Exception e) {
log.error("零钱支付命令生辰: " + e.getMessage(),e); log.error("零钱支付命令生辰: " + e.getMessage(),e);
throw new MallinkException(ErrorCode.SYS_PARAMETER_CAST_ERROR.getCode(), "零钱支付签名异常");
return new CashOutAdapterResult(false, EnumCashOutStatus.FAIL.getCode(), "零钱支付签名异常", null);
} }


String signAgent = WxPayment.createSign(signMap, payAccount.getApiKey()); String signAgent = WxPayment.createSign(signMap, payAccount.getApiKey());
@@ -206,7 +208,7 @@ public class WxCashOutAdapterService implements CashOutAdapterService{
response = WxPay.transfers(signMap, payAccount.getCertPath(), payAccount.getMchId()); response = WxPay.transfers(signMap, payAccount.getCertPath(), payAccount.getMchId());
} catch (Exception e) { } catch (Exception e) {
log.error("零钱支付异常: " + e.getMessage(),e); log.error("零钱支付异常: " + e.getMessage(),e);
throw new MallinkException(ErrorCode.REFUND_ORDER_ERROR.getCode(), "零钱支付异常");
return new CashOutAdapterResult(false, EnumCashOutStatus.FAIL.getCode(), "零钱支付异常", null);
} }
log.info("微信零钱支付:" + wxCashOutP.toString() + ", response: " + response.toString()); log.info("微信零钱支付:" + wxCashOutP.toString() + ", response: " + response.toString());
return getReusltFromp(response,cashOut); return getReusltFromp(response,cashOut);
@@ -292,7 +294,7 @@ public class WxCashOutAdapterService implements CashOutAdapterService{
String result_no = returnMap.get("result_code"); String result_no = returnMap.get("result_code");
if ("SUCCESS".equals(result_no)) { if ("SUCCESS".equals(result_no)) {
String realStatus = returnMap.get("status"); String realStatus = returnMap.get("status");
if ("SUCCESS".equals(realStatus)) {
if ("SUCCESS".equals(realStatus) || "PROCESSING".equals(realStatus)) {
return new CashOutAdapterResult(true,EnumCashOutStatus.SUCCESS.getCode(),"微信零钱支付申请成功",returnMap); return new CashOutAdapterResult(true,EnumCashOutStatus.SUCCESS.getCode(),"微信零钱支付申请成功",returnMap);
}else { }else {
String msg = returnMap.get("reason"); String msg = returnMap.get("reason");


+ 15
- 0
mallinkService/src/main/resources/mapper/WxCashOutMapper.xml Просмотреть файл

@@ -40,11 +40,26 @@
<if test=" null != parentTenantId and '' != parentTenantId"> <if test=" null != parentTenantId and '' != parentTenantId">
and `parent_tenant_id` = #{parentTenantId} and `parent_tenant_id` = #{parentTenantId}
</if> </if>
<if test=" null != createDateBegin">
and `create_date` &gt;= #{createDateBegin}
</if>
<if test=" null != createDateEnd">
and `create_date` &lt;= #{createDateEnd}
</if>


<if test=" null != status "> <if test=" null != status ">
and `status` = #{status} and `status` = #{status}
</if> </if>


<if test=" null != statusList ">
and status in
<foreach collection="statusList" index="index" item="statusItem" open="(" separator="," close=")">
#{statusItem}
</foreach>
</if>

<if test=" null != ids "> <if test=" null != ids ">
and id in and id in
<foreach collection="ids" index="index" item="idItem" open="(" separator="," close=")"> <foreach collection="ids" index="index" item="idItem" open="(" separator="," close=")">


Загрузка…
Отмена
Сохранить