Alipay+Alipay+

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:

幻灯片1.PNG

Figure 1. The process of updating code rules

The process consists of the following steps:

  1. 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.

  1. The MPP calls the inquiryCodeRules API to obtain Alipay+ code rules.
  2. 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:

幻灯片2.PNG

Figure 2. The process of matching code rules

The process consists of the following steps:

  1. The MPP server receives the code value.
  2. 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 matched
  • REGEX: to match by using the regular expression
  • EMVCO: 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.

copy
{
    "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.

copy
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.

copy
{
    "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.

copy
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.

copy
{
    "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.

copy
/**
 * 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.

copy
00020101021226700010com.alipay0152https://qr.kakaopay.com/281006041234567890123456789030990014D410000003000101770000/123456789/1234567890/1234567/12345/1234567/20181017140000123456789012001520441215303410540430005802KR5906TMONEY6004KSCC630461ca

The parsing result is as follows.

copy
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:
幻灯片3.PNG

Figure 3. The process of handling the matching result

The process consists of the following steps:

  1. 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 URL
  • CONSTRUCT_DYNAMIC_URL: to construct a URL based on the dynamic URL expression returned by Alipay+ and open it
  • EXTRACT_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.

copy
{
        "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.

copy
{
        "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.

copy
// 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.

copy
{
        "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.