How to Build a Unified QR Code Page for WeChat, Alipay, and Douyin Mini‑Programs

This article details the design and implementation of a single QR code page that directs users from WeChat, Alipay, and Douyin to appropriate mini‑program pages, covering community joining, user identification via unionid, web authorization, mini‑program launch, and responsive CSS sizing with Taro.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
How to Build a Unified QR Code Page for WeChat, Alipay, and Douyin Mini‑Programs

Requirement Description

Use the same QR code on WeChat, Alipay, and Douyin to enter different pages: Alipay and Douyin go to the ordering mini‑program page and locate the store bound to the QR code, while WeChat goes to a "two‑code integration" page that shows different content based on whether the user has joined the community.

Note: the "two‑code integration" page will be referred to as "two‑code integration" in the following sections.

The basic function of the two‑code integration page is to allow a long‑press on the community QR code to automatically open the WeChat group join page.

Long‑press the community QR code, and WeChat automatically opens the group join page for Guming's community.

Determine whether the user has already joined the Guming community.

Automatically jump to the mini‑program ordering page.

Click a button to jump to the mini‑program ordering page.

Add Community

The community QR code is displayed as an image; long‑pressing the image lets WeChat recognize its content and open the corresponding group join page.

Check if User Joined Community

To identify whether a user who entered the two‑code integration page is the same user who later enters the ordering mini‑program, we use the WeChat public platform's unionid . The relevant identifiers are:

openid : a unique identifier for each user per public account, obtainable without user authorization or following the account.

unionid : a single identifier across multiple public accounts and applications under the same open platform account; it requires the user to follow the account or grant authorization.

Obtaining the unionid allows us to call subsequent APIs to check whether the user has joined the Guming community.

Web Authorization

WeChat Official Account Web Authorization

The typical flow is:

User consents and a code is obtained.

The code is exchanged for an access token, which provides the unionid.

Unlike SaaS systems where domain names are fully controllable, WeChat requires pre‑configuration of authorized domains on the public platform. The following diagram shows the sequence for obtaining the code.

WeChat web authorization flow diagram
WeChat web authorization flow diagram

appid: the public account's appid.

scope: authorization scope, either snsapi_base (silent, only openid) or snsapi_userinfo (shows consent page, returns unionid).

redirect_uri1: the two‑code integration page address, to return after authorization.

redirect_uri2: the address of the page that initiates authorization, to return after authorization.

Authorization link example:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx807d86fb6b3d4fd2&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

We cannot initiate authorization directly from the two‑code integration page because only one authorized domain can be configured, while the page has separate development, testing, and production domains.

To obtain the unionid we need the snsapi_userinfo scope, which shows a consent page, but our user‑experience does not allow pop‑ups.

Enterprise WeChat Web Authorization

Because the official account flow does not meet our requirements, we investigated Enterprise WeChat web authorization. The sequence is similar, but the parameters differ. Example link:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE&agentid=AGENTID#wechat_redirect

The scope can be: snsapi_base: silent authorization, returns the member's basic information (UserId). snsapi_privateinfo: manual authorization, returns detailed information including avatar and QR code.

When the user is an enterprise member, the response includes:

errcode – return code.

errmsg – description of the return code.

userid – member UserID; the address‑book API can be called to read member details.

user_ticket – member ticket (max 512 bytes, valid 1800 s), returned only when snsapi_privateinfo is used and the user is within the app’s visible range.

When the user is not an enterprise member, the response includes:

errcode – return code.

errmsg – description of the return code.

openid – identifier unique to the current enterprise (≤ 64 bytes).

external_userid – external contact ID, returned only if the user is a client of the enterprise and the follower is within the app’s visible range.

Our QR‑code scanners are always non‑enterprise members, so we can obtain external_userid to determine whether the user has joined the community. The solution is: use Enterprise WeChat web authorization to get a code, then exchange it for external_userid.

Jump to Mini‑Program

The most common way to launch a mini‑program from a web page is the open tag wx-open-launch-weapp. When the user has joined the community we want to jump automatically to the ordering mini‑program using a URL Scheme. URL Scheme has a daily limit of 3 million launches, so we use the open tag as a fallback. In the local environment the open tag cannot be debugged; it must be deployed to a test environment, which is limited to three JS‑SDK security domains. An additional pitfall was that the page included JS‑SDK version 1.3.0; the open tag requires at least version 1.6.0, so the tag did not appear even though other APIs worked.

Units and CSS Size

The requirement includes a full‑screen background image with a button hotspot. Assuming a phone screen of 430 × 932 px and a design image of 750 × 1548 px, the button’s original coordinates are (600, 1000) with size 100 × 80 px. Because the aspect ratios differ, the design is set to fill the width and adapt the height. The displayed button width becomes 430 / 750 × 100 ≈ 57.3 px and height 430 / 750 × 80 ≈ 45.8 px, with coordinates (344, 573.3). These values can be applied via absolute CSS positioning, but they must be calculated dynamically for different device resolutions. Instead of manual calculations we can use Taro.pxTransform for runtime conversion.

Taro.pxTransform

Taro.pxTransform

converts design‑draft sizes to runtime units. Its type definition is pxTransform(size: number): string. The source code is:

function (size) {
  const config = taro.config || {};
  // H5 base font size
  const baseFontSize = config.baseFontSize;
  const deviceRatio = config.deviceRatio || defaultDesignRatio;
  const designWidth = ((input = 0) =>
    isFunction(config.designWidth)
      ? config.designWidth(input)
      : config.designWidth || defaultDesignWidth)(size);
  if (!(designWidth in deviceRatio)) {
    throw new Error(`deviceRatio configuration missing setting for ${designWidth}`);
  }
  const targetUnit = config.targetUnit || defaultTargetUnit;
  const unitPrecision = config.unitPrecision || defaultUnitPrecision;
  const formatSize = ~~size;
  let rootValue = 1 / deviceRatio[designWidth];
  switch (targetUnit) {
    case 'rem':
      rootValue *= baseFontSize * 2;
      break;
    case 'px':
      rootValue *= 2;
      break;
  }
  let val = formatSize / rootValue;
  if (unitPrecision >= 0 && unitPrecision <= 100) {
    val = Number(val.toFixed(unitPrecision));
  }
  return val + targetUnit;
}

For a design size of 430, the function returns 10.75rem (430 / (2 × 20)).

Root Element Font Size

The root element font size is calculated as:

const baseFontSize = options?.baseFontSize || 20;
const designWidth = options?.designWidth || 750;
const rootValue = baseFontSize / config.deviceRatio![designWidth!] * 2;
function f() {
  var e = window.document.documentElement;
  var width = Math.floor(e.getBoundingClientRect().width);
  if (width < 600) {
    var x = rootValue * width / designWidth;
    e.style.fontSize = x + "px";
  } else if (width < 840) {
    width = designWidth / 2;
    var x = rootValue * width / designWidth;
    e.style.fontSize = x + "px";
  } else if (width < 1440) {
    width = designWidth / 2;
    var x = rootValue * width / designWidth;
    e.style.fontSize = x + "px";
  } else {
    width = designWidth / 2;
    var x = rootValue * width / designWidth;
    e.style.fontSize = x + "px";
  }
}
window.addEventListener("resize", f);

f();

On a 430 px wide device the root font size becomes approximately 22.93 px (40 × 430 / 750).

Formula Derivation

The conversion can be expressed as:

const designWidth = 750;
const clientWidth = 430;
const sw = 100; // button width in design
const sh = 80;  // button height in design
const sx = 600; // button x in design
const sy = 1000; // button y in design

const ratio = clientWidth / designWidth;
const dw = sw * ratio;
const dh = sh * ratio;
const dx = sx * ratio;
const dy = sy * ratio;

Using Taro.pxTransform(size) * rootFontSize yields the same result because the formula simplifies to size * ratio. Therefore, as long as the design width matches Taro’s default (750 px) or is configured accordingly, the transformation aligns with manual calculations.

Font Size Base

The baseFontSize (default 20) is the H5 font‑size baseline and is typically decided by the visual designer together with the design width.

Summary

During development the most painful issues were debugging the JS‑SDK open tag, which required publishing to a test environment due to security‑domain restrictions, and understanding why Taro.pxTransform(size) * rootFontSize produced the same hotspot coordinates as manual calculations; the article explains this by deriving the underlying formula and showing how the root font size is computed dynamically from baseFontSize and the device width.

frontendQR codeWeChatmini-programweb-auth
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.