Merchant Payment to User
Overview
When a mini-app needs to make a payment to a specified user, call this interface.
Mini-App Backend Service Example Code
OrderService Implementation
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 Implementation
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;
/**
* Mini-App Payment Common Service
*
* @author tom
*/
@Slf4j
@Component
public class MiniAppPayService {
/**
* MOS mini-app base URL
*/
@Value("${mos.mini-app.base-url}")
private String baseUrl;
/**
* MOS mini-app appKey
*/
@Value("${mos.mini-app.app-key}")
private String appKey;
/**
* MOS mini-app 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);
}
/**
* Call MOS server mini-app open API
*
* @param uri API URI
* @param ao Request parameters
* @param clazz Response type
* @return Response object
*/
private <T> T call(String uri, BaseSignedAo ao, Class<T> clazz) {
// Signature
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);
// Make request
String url = this.baseUrl + uri;
ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, ao, String.class);
if (responseEntity.getStatusCode() != HttpStatus.OK || responseEntity.getBody() == null) {
log.error("Mini-app request exception uri={} ao={}", uri, JSON.toJSONString(ao));
return null;
}
// Parse response code(int) message(String) data(T)
JSONObject result = JSON.parseObject(responseEntity.getBody());
if ((int)result.get("code") != 200) {
log.error("Mini-app request exception 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 {
/**
* Mini-app appKey
*/
@NotNull
private String appKey;
/**
* Signature
*/
@NotNull
private String sign;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class PayToMiniAppUserAo extends BaseSignedBo {
/**
* Random string, must be unique in the system
*/
private String nonceStr;
/**
* Merchant mini-app system order number
*/
@NotNull
private String outTradeNo;
/**
* Currency unit USD-US dollar KHR-Cambodian riel
*/
@NotNull
private String currency;
/**
* Order amount
*/
@NotNull
private String amount;
/**
* Openid
*/
@NotNull
private String openid;
}
@Data
public PayToMiniAppUserVo extends BaseSignedBo {
/**
* Random string, must be unique in the system
*/
private String nonceStr;
/**
* Merchant mini-app system order number
*/
private String outTradeNo;
/**
* MOS order number
*/
private String paymentNo;
}
public static class MiniAppUtil {
/**
* Generate signature
*/
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 does not participate in signature && empty values do not participate in signature
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());
}
}
}Additional Notes
The common module of the mini-app backend service provides general login methods, corresponding authentication interceptors and methods to obtain user login information. For details, please refer to the "Mini-App Backend Development" chapter.