System Overview
The WeChat Small Merchant API enables merchants to register, check status, sign contracts, configure information, initiate payments via QR codes and mini-programs, manage orders, process refunds, and handle withdrawals.
Merchant Registration Process
Common Issues and Solutions
1. API Error Responses
- Certificate Not Found: Ensure correct merchant ID input. Some accounts have multiple IDs - try alternative credentials.
- Sensitive Data Decryption Failure: Verify all required parameters are properly encrypted, particularly phone numbers.
- Signature Validation Failure: Place the sign parameter last in the request, not following API parameter order.
- ID Card Recognition Failure: Expired or invalid identification documents will cause recognition failures.
2. Java AES Encryption Exceptions
Download and replace local_policy.jar and US_export_policy.jar files in ${jdk_home}/jre/lib/security and ${jre_home}/lib/security directories from Oracle's official website to resolve encryption policy restrictions.
3. Registration Workflow
- Application Submission: Retrieve platform certificate, encrypt sensitive data, upload images for media IDs, populate parameters, and sign requests using merchant certificate.
- Status Checking: Query using custom reference numbers or WeChat application IDs. Returns approval status and signing links for successful applications.
Merchant Configuration
Configuration Issues
- AppID Binding: The sub_appid parameter must match the mini-program ID for proper payment functionality.
Configuration Procedure
Bind merchant ID with designated mini-program ID to establish payment capabilities.
Transaction Processing
Transaction Challenges
- Parameter Requirements: Different payment methods have varying mandatory parameters - verify documentation carefully.
- Native Payment URL: Generated URLs require QR code conversion; direct browser access is unsupported.
- Refund Limitations: Daily automatic withdrawals may leave insufficient balances for refunds - ensure adequate funds.
- Refund Query Results: Use refund transaction IDs for precise queries, especially when multiple refunds exist per order.
Transaction Flow
- Payment Initiation: Submit unique order ID, product details, amount (in cents), and payment method to receive QR code or mini-program payment parameters.
- Order Query: Retrieve order status using merchant ID and WeChat/merchant order numbers.
- Order Closure: Close unpaid orders after minimum 5-minute interval using merchant ID and order reference.
- Refund Processing Submit WeChat/merchant order IDs, unique refund ID, total amount, and refund amount. Reuse original refund ID for retries.
- Refund Status Check: Query using refund transaction ID for accurate results.
Profile Management and Withdrawals
Modification and Withdrawal Procedures
- Withdrawal Query: Specify merchant ID and previous day's date (withdrawals process previous day's transactions).
- Bank Account Update: Provide merchant ID, bank name, and encrypted account number for modification.
- Withdrawal Retry: For transactions dated January 1st, query using January 2nd as the withdrawal date.
- Contact Information: Update merchant contact details through designated API endpoints.
Technical Implementation
The system employs SSM (Spring-Spring MVC-MyBatis) architecture with the following core components:
Controller Layer
@RestController
public class MerchantApiController {
@Autowired
private MerchantTransactionService transactionService;
@PostMapping("/media/upload")
public ResponseModel uploadMedia(@RequestBody MediaUploadRequest request) {
return transactionService.processMediaUpload(request);
}
@PostMapping("/application/status")
public ResponseModel checkApplication(@RequestBody ApplicationStatusRequest request) {
return transactionService.queryApplicationStatus(request);
}
}
Service Implementation
@Service
public class MerchantRegistrationServiceImpl implements MerchantRegistrationService {
private final Logger logger = LoggerFactory.getLogger(getClass());
public SortedMap<String, String> prepareRegistration(RegistrationData data) throws Exception {
String certificateData = fetchPlatformCertificate();
if(certificateData.isEmpty()) return null;
CertificateInfo certInfo = parseCertificateData(certificateData);
data.setCertificateSerial(certInfo.getSerialNumber());
data.setEncryptedName(encryptData(data.getLegalName(), certInfo.getPublicKey()));
SortedMap<String, String> parameters = convertToSortedMap(data);
parameters.put("sign", generateSignature(parameters, merchantKey));
return parameters;
}
}
HTTP Utility Class
public class SecureHttpClient {
public static String executeSecurePost(String endpoint, Map<String, String> params,
String certificatePath, String merchantId) {
SSLContext sslContext = createSSLContext(certificatePath, merchantId);
try (CloseableHttpClient client = HttpClients.custom()
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext))
.build()) {
HttpPost request = new HttpPost(endpoint);
request.setEntity(new StringEntity(buildXmlPayload(params)));
return executeRequest(client, request);
}
}
}
Security Utilities
public class SecurityUtils {
public static String rsaEncrypt(String plaintext, String publicKeyCert) {
X509Certificate certificate = loadCertificate(publicKeyCert);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes()));
}
public static String generateHMACSignature(Map<String, String> data, String secret) {
String queryString = data.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getKey() + "=" + entry.getValue())
.collect(Collectors.joining("&"));
return calculateHMACSHA256(queryString + "&key=" + secret, secret);
}
}
XML Processing
public class XmlParser {
public static Map<String, String> parseResponse(String xmlResponse) {
Map<String, String> result = new HashMap<>();
Document document = parseXmlDocument(xmlResponse);
NodeList nodes = document.getDocumentElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
result.put(node.getNodeName(), node.getTextContent());
}
}
return result;
}
}
API Integration Notes
WeChat's API documentation provides comprehensive specifications, though parameter descriptions may require clarification. Successful integration demands careful attention to parameter configuration order and encryption requirements. Experience with this API facilitates adaptation to other payment gateway integrations.