Skip to content

商户给用户付款

概述

微应用需要给指定用户付款时,调用此接口。

微应用后端服务示例代码

OrderService 实现

java
import com.testproject.mos.miniapp.common.api.ApiResult;
import com.testproject.mos.miniapp.common.entity.vo.TokenVo;
import com.testproject.mos.miniapp.vet.ao.LoginAo;
import org.springframework.validation.annotation.Validated;

@Service
public class OrderService {

    @Resource
    private MiniAppPayService miniAppPayService;
        
    public String payToMiniAppUser(String currency, String amount, String realOrderId, String orderId, String dstOpenId) {
        PayToMiniAppUserAo payToMiniAppUserAo = new PayToMiniAppUserAo();
        payToMiniAppUserAo.setCurrency(currency);
        payToMiniAppUserAo.setAmount(amount);
        payToMiniAppUserAo.setNonceStr(MiniAppUtil.generateUuidStr());
        payToMiniAppUserAo.setOutTradeNo(orderId);
        payToMiniAppUserAo.setOpenid(dstOpenId);
        PayToMiniAppUserVo payToMiniAppUserVo = miniAppPayService.payToMiniAppUser(payToMiniAppUserAo);
    }
}

MiniAppPayService 实现

java
import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;

import java.util.Map;
import java.util.TreeMap;

/**
 * 微应用支付公共服务
 *
 * @author tom
 */
@Slf4j
@Component
public class MiniAppPayService {

    /**
     * Mos微应用的调用地址
     */
    @Value("${mos.mini-app.base-url}")
    private String baseUrl;
    /**
     * Mos微应用的appKey
     */
    @Value("${mos.mini-app.app-key}")
    private String appKey;
    /**
     * Mos微应用的appSecret
     */
    @Value("${mos.mini-app.app-secret}")
    private String appSecret;

    private final RestTemplate restTemplate;

    public MiniAppPayService() {
        SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
        httpRequestFactory.setConnectTimeout(30000);
        httpRequestFactory.setReadTimeout(60000);
        this.restTemplate = new RestTemplate(httpRequestFactory);
    }

    /**
     * 请求Mos服务端微应用开放接口
     *
     * @param uri          接口URI
     * @param ao           请求参数
     * @param clazz 响应类型
     * @return 响应对象
     */
    private <T> T call(String uri, BaseSignedAo ao, Class<T> clazz) {
        // 签名
        ao.setAppKey(this.appKey);

        Map<String, String> data = JSON.parseObject(JSON.toJSONString(ao), new TypeReference<Map<String, String>>() {
        });
        String sign = MiniAppUtil.generateSignature(data, this.appSecret);
        ao.setSign(sign);

        // 发起请求
        String url = this.baseUrl + uri;
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, ao, String.class);
        if (responseEntity.getStatusCode() != HttpStatus.OK || responseEntity.getBody() == null) {
            log.error("微应用请求异常 uri={} ao={}", uri, JSON.toJSONString(ao));
            return null;
        }

        // 解析响应 code(int) message(String) data(T)
        JSONObject result = JSON.parseObject(responseEntity.getBody());
        if ((int)result.get("code") != 200) {
            log.error("微应用请求异常 uri={} ao={} result={}", uri, JSON.toJSONString(ao), JSON.toJSONString(result));
            return null;
        }
        return JSON.to(clazz, result.get("data"));
    }


    public PrepayOrderVo prepay(CreatePrepayOrderAo ao) {
        return this.call("/open-apis/mp/v1/pay/prepay", ao, PrepayOrderVo.class);
    }

    public PayToMiniAppUserVo payToMiniAppUser(PayToMiniAppUserAo ao) {
        return this.call("/open-apis/mp/v1/pay/payToMiniAppUser", ao, PayToMiniAppUserVo.class);
    }
    public OrderQueryVo orderQuery(OrderQueryAo ao) {
        return this.call("/open-apis/mp/v1/pay/orderQuery", ao, OrderQueryVo.class);
    }

    @Data
    public static class BaseSignedAo {

        /**
         * 微应用 appKey
         */
        @NotNull
        private String appKey;

        /**
         * 签名
         */
        @NotNull
        private String sign;
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PayToMiniAppUserAo extends BaseSignedBo {
    
        /**
         * 随机字符串,需保证系统内唯一
         */
        private String nonceStr;
    
        /**
         * 商户小程序系统订单号
         */
        @NotNull
        private String outTradeNo;
    
        /**
         * 货币单位 USD-美元 KHR-瑞尔
         */
        @NotNull
        private String currency;
    
        /**
         * 订单金额
         */
        @NotNull
        private String amount;
    
        /**
         * openid
         */
        @NotNull
        private String openid;
    
    }

    @Data
    public PayToMiniAppUserVo extends BaseSignedBo {

        /**
         * 随机字符串,需保证系统内唯一
         */
        private String nonceStr;
        
        /**
         * 商户小程序系统订单号
         */
        private String outTradeNo;
    
        /**
         * mos订单号
         */
        private String paymentNo;
    
    }

    public static class MiniAppUtil {

        /**
         * 生成签名
         */
        public static String generateSignature(Map<String, String> data, String appSecret) {
            // 1. Sort the parameters alphabetically
            Map<String, String> sortedData = new TreeMap<>(data);

            // 2. Concatenate the parameters in the format key1=value1&key2=value2...
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> entry : sortedData.entrySet()) {
                if ("sign".equals(entry.getKey()) || entry.getValue() == null || entry.getValue().isEmpty()) {
                    // sign 不参与签名 && 空值不参与签名
                    continue;
                }
                sb.append(entry.getKey()).append("=").append(entry.getValue().trim()).append("&");
            }
            sb.append("secret=").append(appSecret);

            // 3. Hash the concatenated string using MD5
            return new MD5().digestHex(sb.toString());
        }

    }
}

其他说明

微应用后端服务的公共模块提供了通用的登录方法,及其相应的鉴权拦截器和和获取用户登录的方法,详见《微应用后端开发》章节。