Alipay+ code rules
Overview
For Mobile Payment Partners (MPPs) who don't want to use the Alipay+ server SDK, they can call the inquiryCodeRules API that is provided by Alipay+ to obtain Alipay+ code rules. Then, the MPP saves and uses Alipay+ code rules locally to identify the code.
Alipay+ code rules are a set of matching rules that are used to identify whether a code can be processed and decoded by Alipay+.
This chapter guides MPPs to use Alipay+ code rules to identify the code locally when receiving a code.
How to use Alipay+ code rules
Step 1: Update Alipay+ code rules
To use Alipay+ code rules, the MPP needs to ensure that the code rules that are cached locally are in the latest version. Thus, it is necessary for the MPP to regularly update the code rules.
The following figure illustrates the process to update the code rules:
Figure 1. The process of updating code rules
The process consists of the following steps:
- The MPP sets a timer to regularly trigger API calls, so as to inquire about Alipay+ code rules.
Note: The frequency of the API calls needs to be at least once per day.
- The MPP calls the inquiryCodeRules API to obtain Alipay+ code rules.
- The MPP server identifies whether the version of obtained Alipay+ code rules is consistent with that of Alipay+ code rules that are cached locally.
- If so, the MPP keeps Alipay+ code rules that are cached locally.
- If not, the MPP updates Alipay+ code rules that are cached locally to the latest version.
Step 2: Identify the code by matching Alipay+ code rules
When the user scans a QR code, the MPP needs to identify whether the code can be decoded with Alipay+ code rules, and respectively take further actions.
The following figure illustrates the process:
Figure 2. The process of matching code rules
The process consists of the following steps:
- The MPP server receives the code value.
- The MPP server searches for and tries to match the code value with Alipay+ code rules, so as to identify whether the code can be decoded with these code rules. For more information about how to match, see How to match Alipay+ code rules.
- If so, the MPP server checks the value of postCodeMatchAction.postCodeMatchActionType as stated in the respective code rule that is returned in the inquiryCodeRules API, and takes further actions. For more information about what further actions need to be taken for different values, see Step 3: Handle the matching result.
- If not, the MPP server continues searching for Alipay+ code rules.
How to match Alipay+ code rules
Alipay+ code rules support the following three matching methods that are returned by Alipay+ for the codeRule.codeMatchPattern.MatchMethod parameter in the inquiryCodeRules API:
PREFIX
: to match by checking whether the prefix is matchedREGEX
: to match by using the regular expressionEMVCO
: to match by following the EMV code specifications
PREFIX
The following sample shows the code rule when the value of codeRule.codeMatchPattern.MatchMethod is PREFIX
.
{
"codeMatchPattern": {
"matchMethod": "PREFIX",
"prefix": "http://qr.ap.dev.alipay.net/1633195255321"
},
"postCodeMatchAction": {
"postCodeMatchActionType": "DECODE"
}
}
With such a code rule, the MPP can use the String.StartsWith method to identify whether the code value can be decoded as follows.
String codeValue = "";
if(codeValue.startsWith(codeMatchPattern.prefix)) {
// do A+ decode
}
REGEX
The following sample shows the code rule when the value of codeRule.codeMatchPattern.MatchMethod is REGEX
.
{
"codeMatchPattern": {
"matchMethod": "REGEX",
"regex": "^https?:\\/\\/(qr-test-entry-code|app-cscan|vmp|1633984529766)\\.(eftpay|eftsolutions|o2pcn)\\.com(\\.(cn|hk))?\\/.*"
},
"postCodeMatchAction": {
"postCodeMatchActionType": "DECODE"
}
}
With such a code rule, the MPP can use the regular expression to identify whether the code value can be decoded as follows.
String codeValue = "";
Pattern pattern = Pattern.compile(codeMatchPattern.regex);
if(pattern.matcher(codeValue).matches()) {
// do A+ decode
}
EMVCO
The following sample shows the code rule when the value of codeRule.codeMatchPattern.MatchMethod is EMVCO
.
{
"codeMatchPattern": {
"emvMatchRules": [{
"extendedMerchantTag": "00",
"extendedMerchantValue": "com.alipay",
"isPrimitiveMerchant": "false"
}],
"matchMethod": "EMVCO"
},
"postCodeMatchAction": {
"postCodeMatchActionType": "DECODE"
}
}
With such a code rule, the MPP can run the following code to identify whether the code value can be decoded.
/**
* Alipay.com Inc.
* Copyright (c) 2004-2019 All Rights Reserved.
*/
package ac.code.utils;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import ac.framework.enums.MobileResultCode;
import org.apache.commons.lang3.StringUtils;
import ac.code.model.emvco.EMVCoQrField;
import ac.framework.exception.MobileBizException;
import ac.framework.utils.AssertUtil;
/**
* @author cover.dh
* @version $Id: EMVCoCoder.java, v 0.1 2019-10-02 3:34 PM cover.dh Exp $$
*/
public class EMVCoUtils {
/** tag 02 ~ tag 25 : primitive MAI */
private static final int PRIMITIVE_MAI_START_TAG = 2;
/** tag 02 ~ tag 25 : primitive MAI */
private static final int PRIMITIVE_MAI_END_TAG = 25;
/** tag 26 ~ tag 51 : extend MAI */
private static final int EXTEND_MAI_END_TAG = 51;
/** Tag fixed length */
private static final int T_FIX_LENGTH = 2;
/** length fixed length */
private static final int L_FIX_LENGTH = 2;
/** EMVCo min length */
private static final int MIN_EMVCO_TOTAL_LENGTH = 5;
/** emvco prefix */
private static final String EMVCO_CODE_PREFIX = "0002";
/**
* Parse emv co standard code emv co parse info.
*
* @param code the code
* @return the emv co parse info
*/
public static EMVCoParseInfo parse(String code) {
if (!StringUtils.startsWith(code, EMVCO_CODE_PREFIX)) {
return null;
}
Map<Integer, EMVCoQrField> resultMap = convertToMap(code);
TreeSet<EMVCoQrField> primitiveMaiList = new TreeSet<EMVCoQrField>();
TreeSet<EMVCoQrField> extendMaiList = new TreeSet<EMVCoQrField>();
for (int i = PRIMITIVE_MAI_START_TAG; i <= EXTEND_MAI_END_TAG; i++) {
EMVCoQrField qrField = resultMap.get(i);
if (qrField != null) {
if (i <= PRIMITIVE_MAI_END_TAG) {
primitiveMaiList.add(qrField);
} else {
extendMaiList.add(qrField);
}
}
}
EMVCoParseInfo emvCoParseInfo = new EMVCoParseInfo(true);
emvCoParseInfo.setPrimitiveMai(primitiveMaiList);
emvCoParseInfo.setExtendMai(extendMaiList);
return emvCoParseInfo;
}
/**
* convert code to EMVCoFieldMap
*
* @param code the code
* @return null : not EMVCo code
* @throws MobileBizException the mobile biz exception
*/
private static Map<Integer, EMVCoQrField> convertToMap(String code) {
final int codeTotalLength = code.length();
AssertUtil.isTrue(codeTotalLength >= MIN_EMVCO_TOTAL_LENGTH,
MobileResultCode.UNKNOWN_EXCEPTION,
String.format("EMVCoParser WARN: length too short, length: %d, code value: %s",
codeTotalLength, code));
Map<Integer, EMVCoQrField> result = new HashMap<Integer, EMVCoQrField>();
int tagStartPos = 0;
while (tagStartPos < codeTotalLength) {
int tagEndPos = tagStartPos + T_FIX_LENGTH;
endPositionCheck(tagEndPos, codeTotalLength);
int lenStartPos = tagEndPos;
int lenEndPos = lenStartPos + L_FIX_LENGTH;
endPositionCheck(lenEndPos, codeTotalLength);
// tag must be numeric
final String tagContent = code.substring(tagStartPos, tagEndPos);
AssertUtil.isTrue(StringUtils.isNumeric(tagContent), MobileResultCode.UNKNOWN_EXCEPTION,
String.format("EMVCoParser WARN: tag is not numeric, tag: %s, code value: %s",
tagContent, code));
final String lenContent = code.substring(lenStartPos, lenEndPos);
AssertUtil.isTrue(StringUtils.isNumeric(lenContent), MobileResultCode.UNKNOWN_EXCEPTION,
String.format("EMVCoParser WARN: length is not numeric, length: %s, code value: %s",
lenContent, code));
int valContentLength = Integer.parseInt(lenContent);
AssertUtil.isTrue(valContentLength > 0, MobileResultCode.UNKNOWN_EXCEPTION,
String.format("length <= 0, length:%d, code: %s", valContentLength, code));
AssertUtil.isTrue(valContentLength <= codeTotalLength - 4,
MobileResultCode.UNKNOWN_EXCEPTION, "not tlv");
int valStartInd = lenEndPos;
int valEndInd = valStartInd + valContentLength;
endPositionCheck(valEndInd, codeTotalLength);
String valContent = StringUtils.substring(code, valStartInd, valEndInd);
EMVCoQrField emvCoQrField = new EMVCoQrField(tagContent, valContentLength, valContent);
result.put(Integer.parseInt(tagContent), emvCoQrField);
tagStartPos = valEndInd;
}
return result;
}
/**
* end position check
*
* @param currentPos the current pos
* @param codeTotalLength the code total length
*/
private static void endPositionCheck(int currentPos, int codeTotalLength) {
AssertUtil.isTrue(currentPos <= codeTotalLength, MobileResultCode.UNKNOWN_EXCEPTION,
"not tlv, length exceed");
}
/**
* get EMVCo Field value
*
* @param code the code
* @param tag the tag
* @return the field value
*/
public static String getFieldValue(String code, String tag) {
if (!StringUtils.isNumeric(tag)) {
return null;
}
return getFieldValue(code, Integer.parseInt(tag));
}
/**
* get EMVCo Field Value
*
* @param code the code
* @param tag the tag
* @return the field value
*/
private static String getFieldValue(String code, int tag) {
EMVCoQrField field = getField(code, tag);
if (field == null) {
return null;
}
return field.getValue();
}
/**
* get EMVCo Qr Field
*
* @param code the code
* @param tag the tag
* @return field field
*/
private static EMVCoQrField getField(String code, int tag) {
if (tag < 0) {
return null;
}
Map<Integer, EMVCoQrField> map = convertToMap(code);
if (map == null) {
return null;
}
return map.get(tag);
}
@Getter
@Setter
public static class EMVCoParseInfo extends ToString {
/** serialVersionUID */
private static final long serialVersionUID = 1630940991321569846L;
/**
* this code is emvco code
*/
private boolean isEMVCoCode = false;
/**
* Merchant Account Information (IDs "02" to "25")
*/
private TreeSet<EMVCoQrField> primitiveMai;
/**
* Merchant Account Information (IDs "26" to "51")
*/
private TreeSet<EMVCoQrField> extendMai;
public EMVCoParseInfo(boolean isEMVCoCode) {
this.isEMVCoCode = isEMVCoCode;
}
}
@Getter
@Setter
@EqualsAndHashCode
public static class EMVCoQrField extends ToString implements Comparable<EMVCoQrField> {
/** serialVersionUID */
private static final long serialVersionUID = 7548764562673462948L;
/** Tag */
private String tag;
/** Length */
private int length;
/** Value */
private String value;
/**
* Instantiates a new Emv co qr field. TODO: check the length
*
* @param tag the tag
* @param length the length
* @param value the value
*/
public EMVCoQrField(String tag, int length, String value) {
this.tag = tag;
this.length = length;
this.value = value;
}
@Override
public int compareTo(EMVCoQrField o) {
return tag.compareTo(o.tag);
}
}
}
Take the EMV code of Korean KSCC as an example.
00020101021226700010com.alipay0152https://qr.kakaopay.com/281006041234567890123456789030990014D410000003000101770000/123456789/1234567890/1234567/12345/1234567/20181017140000123456789012001520441215303410540430005802KR5906TMONEY6004KSCC630461ca
The parsing result is as follows.
00 02 01
01 02 12
26 70
00 10 com.alipay
01 52 https://qr.kakaopay.com/2810060412345678901234567890
30 99
00 14 D4100000030001
01 77 0000/123456789/1234567890/1234567/12345/1234567/20181017140000123456789012001
52 04 4121
53 03 410
54 04 3000
58 02 KR
59 06 TMONEY
60 04 KSCC
63 04 61ca
Notes:
- When the value of matchPattern.matchType is
EMVCO
, the code is an EMV code and needs to be identified according to the EMV specifications. The EMV specifications are applicable for TLV-format codes. In the parsing result above, the three columns separated by spaces from left to right are: T (tag), L (length), V (value). In the columns where the value of T is 26 to 51, the value of V can also be in a TLV format.- For more information about EMV QR codes, see EMVCo website or 📎EMV QRCPS.pdf.
The names and descriptions of some Ts are as follows.
ID | Name | Format | Length | Presence | Comment |
00 | Payload Format Indicator | N | 02 | M | |
01 | Point of Initiation Method | N | 02 | O | |
02-51 | Merchant Account Information | ans | Each var. up to "99" | M | At least one Merchant Account Information data object shall be present. |
52 | Merchant Category Code | N | 04 | M | |
53 | Transaction Currency | N | 03 | M | |
54 | Transaction Amount | ans | var. up to "13" | C | Absent if the mobile application is to prompt the consumer to enter the transaction amount. Present otherwise. |
55 | Tip or Convenience Indicator | N | 02 | O | |
56 | Value of Convenience Fee Fixed | ans | var. up to "13" | C | Presence of these data objects depends on the presence and value of the Tip or Convenience Indicator. |
57 | Value of Convenience Fee Percentage | ans | var. up to "05" | C | |
58 | Country Code | ans | "02" | M | |
59 | Merchant Name | ans | var. up to "25" | M |
Step 3: Handle the matching result
If the code matches a certain Alipay+ code rule, the MPP needs to handle the matching result according to the code rule.
The following figure illustrates the process to handle the matching result:
Figure 3. The process of handling the matching result
The process consists of the following steps:
- The MPP server checks the value of postCodeMatchAction.postCodeMatchActionType in the respective code rule that is returned by Alipay+ in the inquryCodeRules API.
- If the value of postCodeMatchAction.postCodeMatchActionType is
DECODE
, the MPP server calls the userInitiatedPay API to send a decoding request to Alipay+. For more information about how to call this API, see Decode the code value.
- If the value of postCodeMatchAction.postCodeMatchActionType is
OPEN_URL
, the MPP server needs to open the URL. For more information about how to open the URL, see How to open the URL.
How to open the URL
If the value of postCodeMatchAction.postCodeMatchActionType is OPEN_URL
, the MPP server needs to open the URL according to the value of postCodeMatchAction.urlConstructionMethod. For this parameter, valid values are:
USE_DIRECTLY
: to directly open the URLCONSTRUCT_DYNAMIC_URL
: to construct a URL based on the dynamic URL expression returned by Alipay+ and open itEXTRACT_URL_FROM_EMVCODE
: to extract the value of emvUrlTag to construct a URL and open it
USE_DIRECTLY
The following sample shows the code rule when the value of postCodeMatchAction.urlConstructionMethod is USE_DIRECTLY
.
{
"codeMatchPattern": {
"matchMethod": "REGEX",
"regex": "^https?:\\/\\/(qr-test-entry-code|app-cscan|vmp|1633984529766)\\.(eftpay|eftsolutions|o2pcn)\\.com(\\.(cn|hk))?\\/.*"
},
"postCodeMatchAction": {
"postCodeMatchActionType": "OPEN_URL",
"urlConstructionMethod": "USE_DIRECTLY",
"userAgent": "AlipayConnect"
}
}
With such a code rule, the MPP can open the URL that is contained in the code value directly.
CONSTRUCT_DYNAMIC_URL
The following sample shows the code rule when the value of postCodeMatchAction.urlConstructionMethod is CONSTRUCT_DYNAMIC_URL
.
{
"codeMatchPattern": {
"emvMatchRules": [{
"extendedMerchantTag": "00",
"extendedMerchantValue": "SG.COM.NETS",
"isPrimitiveMerchant": "false"
}],
"matchMethod": "EMVCO"
},
"postCodeMatchAction": {
"dynamicUrlExpression": "http://rendertest.alipay.com/p/yuyan/180020010001195821/shop.html?region=SG&tntInstId=ALIPW3SG&codeValue=%s",
"postCodeMatchActionType": "OPEN_URL",
"urlConstructionMethod": "CONSTRUCT_DYNAMIC_URL",
"userAgent": "AlipayConnect"
}
}
With such a code rule, the MPP can run the following code to construct a URL and open it.
// codeValue that is sent by the MPP client
String encodeCodeValue = UrlEncoderUtil.encode(codeValue, "UTF_8");
Map<String, String> valuesMap = new HashMap<>();
valuesMap.put("codeValue", encodeCodeValue);
StrSubstitutor sub = new StrSubstitutor(valuesMap);
return sub.replace(baseUrl);
EXTRACT_URL_FROM_EMVCODE
The following sample shows the code rule when the value of postCodeMatchAction.urlConstructionMethod is EXTRACT_URL_FROM_EMVCODE
.
{
"codeMatchPattern": {
"matchMethod": "REGEX",
"regex": "^https?:\\/\\/(qr-test-entry-code|app-cscan|vmp|1633984529766)\\.(eftpay|eftsolutions|o2pcn)\\.com(\\.(cn|hk))?\\/.*"
},
"postCodeMatchAction": {
"postCodeMatchActionType": "OPEN_URL",
"urlConstructionMethod": "USE_DIRECTLY",
"userAgent": "AlipayConnect"
}
}
With such a code rule, the MPP needs to extract the value of emvUrlTag to construct a URL and open it. For more information about EMV QR codes, see EMVCo website or 📎EMV QRCPS.pdf.
More information
For more information about the API, see inquiryCodeRules.