> ## Documentation Index
> Fetch the complete documentation index at: https://docs.xanhcard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sử dụng máy quét

> Hướng dẫn từng bước để sử dụng các chức năng của máy quét

export const PhoneMockup = ({children, wallpaper, screenBg, time = "9:41", date = "Today", showClock = false, darkStatus = false, hideStatusBar = false, hideNotch = false, hideSideButton = false, walletBg = false, animate = true, scale = 1, style}) => {
  const CSS = [".xp-phone{width:260px;height:530px;position:relative;flex-shrink:0;transform-origin:top center}", ".xp-phone-body{background:#0a0a0a;border-radius:34px;position:absolute;inset:0;box-shadow:inset 0 0 0 2px #262626,0 4px 10px rgba(0,0,0,.12),0 24px 48px -12px rgba(0,0,0,.55)}", ".xp-notch{z-index:5;background:#000;border-radius:10px;width:70px;height:19px;position:absolute;top:12px;left:50%;transform:translateX(-50%)}", ".xp-screen{background:#0a0a0e;border-radius:27px;position:absolute;inset:7px;overflow:hidden}", ".xp-wallpaper{object-fit:cover;filter:brightness(.55) saturate(.9);z-index:0;width:100%;height:100%;position:absolute;inset:0}", ".xp-walletbg{z-index:2;pointer-events:none;background:linear-gradient(#f5f5f7 0%,#e5e5ea 100%);position:absolute;inset:0}", ".xp-statusbar{letter-spacing:.02em;z-index:4;color:#fff;justify-content:space-between;align-items:center;font-size:10px;display:flex;position:absolute;top:13px;left:18px;right:18px}", ".xp-statusbar-time{font-weight:600}", ".xp-statusbar-icons{opacity:.9;align-items:center;gap:5px;display:inline-flex}", ".xp-statusbar-dark{color:#0a0a0a}", ".xp-lockscreen{text-align:center;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.25);z-index:1;position:absolute;top:60px;left:0;right:0}", ".xp-lockscreen-date{letter-spacing:.01em;font-family:-apple-system,'SF Pro Rounded','SF Pro Display',system-ui,sans-serif;font-size:13px;font-weight:600;line-height:1.1}", ".xp-lockscreen-time{letter-spacing:-.045em;margin-top:-4px;font-family:-apple-system,'SF Pro Rounded','SF Pro Display',system-ui,sans-serif;font-size:82px;font-weight:700;line-height:1}", ".xp-sidebutton{z-index:3;background:#1a1a1a;border-radius:2px 0 0 2px;width:5px;height:64px;position:absolute;top:116px;right:-3px;box-shadow:inset 1px 0 #2a2a2a}", ".xp-pass-anim{position:absolute;top:44px;left:13px;right:13px;z-index:3}", ".xp-notif-anim{position:absolute;top:210px;left:11px;right:11px;z-index:3}", ".xp-pill-anim{position:absolute;top:178px;left:0;right:0;text-align:center;z-index:3}", ".xp-glance-anim{position:absolute;bottom:16px;left:11px;right:11px;z-index:3}", ".xp-phone:not(.xp-in):not(.xp-static) .xp-pass-anim{opacity:0;transform:translateY(-130%)}", ".xp-phone:not(.xp-in):not(.xp-static) .xp-notif-anim{opacity:0;transform:translateY(-160%)}", ".xp-phone:not(.xp-in):not(.xp-static) .xp-pill-anim,.xp-phone:not(.xp-in):not(.xp-static) .xp-glance-anim{opacity:0}", ".xp-phone.xp-in .xp-pass-anim{animation:xp-pass-drop 6.4s cubic-bezier(.2,.8,.2,1) infinite}", ".xp-phone.xp-in .xp-notif-anim{animation:xp-notif-drop 7s cubic-bezier(.2,.8,.2,1) infinite}", ".xp-phone.xp-in .xp-pill-anim{animation:xp-pill-rise 6.7s cubic-bezier(.2,.8,.2,1) infinite}", ".xp-phone.xp-in .xp-glance-anim{animation:xp-glance-rise 6.7s cubic-bezier(.2,.8,.2,1) infinite}", ".xp-phone.xp-in .xp-sidebutton{animation:xp-btn-press 6.4s ease-out infinite}", ".xp-phone.xp-static .xp-pass-anim,.xp-phone.xp-static .xp-notif-anim,.xp-phone.xp-static .xp-pill-anim,.xp-phone.xp-static .xp-glance-anim{opacity:1;transform:none}", "@keyframes xp-pass-drop{0%,18%{opacity:0;transform:translateY(-130%)}30%,84%{opacity:1;transform:translateY(0)}96%,100%{opacity:0;transform:translateY(-130%)}}", "@keyframes xp-notif-drop{0%,14%{opacity:0;transform:translateY(-160%)}26%,86%{opacity:1;transform:translateY(0)}96%,100%{opacity:0;transform:translateY(-160%)}}", "@keyframes xp-pill-rise{0%,14%{opacity:0;transform:translateY(10px)}26%,86%{opacity:1;transform:translateY(0)}96%,100%{opacity:0;transform:translateY(10px)}}", "@keyframes xp-glance-rise{0%,18%{opacity:0;transform:translateY(20px)}30%,86%{opacity:1;transform:translateY(0)}96%,100%{opacity:0;transform:translateY(20px)}}", "@keyframes xp-btn-press{0%,8%{transform:translateX(0)}10%,12%{transform:translateX(-2px)}14%,16%{transform:translateX(0)}18%,20%{transform:translateX(-2px)}22%,100%{transform:translateX(0)}}", "@media (prefers-reduced-motion:reduce){.xp-phone .xp-pass-anim,.xp-phone .xp-notif-anim,.xp-phone .xp-pill-anim,.xp-phone .xp-glance-anim,.xp-phone .xp-sidebutton{animation:none!important}.xp-phone .xp-pass-anim,.xp-phone .xp-notif-anim{opacity:1!important;transform:none!important}.xp-phone .xp-pill-anim,.xp-phone .xp-glance-anim{opacity:1!important;transform:none!important}}", "@media (max-width:600px){.xp-phone{transform:scale(.68)!important;margin-bottom:-170px}}"].join("");
  const SignalIcon = () => <svg width="12" height="9" viewBox="0 0 12 9" fill="currentColor" aria-hidden="true">
      <rect x="0" y="5" width="2" height="4" rx="0.5" />
      <rect x="3.5" y="3" width="2" height="6" rx="0.5" />
      <rect x="7" y="1" width="2" height="8" rx="0.5" />
      <rect x="10.5" y="0" width="2" height="9" rx="0.5" opacity="0.35" />
    </svg>;
  const WifiIcon = () => <svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" aria-hidden="true">
      <path d="M1.5 3.5a8 8 0 0 1 11 0" />
      <path d="M3.8 5.8a4.8 4.8 0 0 1 6.4 0" />
      <circle cx="7" cy="8.2" r="1.1" fill="currentColor" stroke="none" />
    </svg>;
  const BatteryIcon = () => <svg width="20" height="10" viewBox="0 0 22 11" aria-hidden="true">
      <rect x="0.5" y="0.5" width="18" height="10" rx="2.5" fill="none" stroke="currentColor" strokeOpacity="0.4" />
      <rect x="2" y="2" width="12" height="7" rx="1.5" fill="currentColor" />
      <path d="M20.5 3.5v4a2 2 0 0 0 0-4z" fill="currentColor" fillOpacity="0.4" />
    </svg>;
  const ref = useRef(null);
  useEffect(() => {
    const el = ref.current;
    if (!el || !animate) return;
    const reduced = typeof window !== "undefined" && window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    if (reduced || typeof IntersectionObserver === "undefined") {
      el.classList.add("xp-in");
      return;
    }
    const obs = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          el.classList.add("xp-in");
          obs.disconnect();
        }
      });
    }, {
      threshold: 0.15
    });
    obs.observe(el);
    return () => obs.disconnect();
  }, [animate]);
  const screenStyle = {
    background: screenBg || "#0a0a0e"
  };
  if (!wallpaper && !walletBg && !screenBg) {
    screenStyle.background = "linear-gradient(160deg, #16224a 0%, #0a0a0e 60%)";
  }
  const phoneCls = "xp-phone" + (animate ? "" : " xp-static");
  const statusCls = "xp-statusbar" + (walletBg || darkStatus ? " xp-statusbar-dark" : "");
  return <div style={Object.assign({
    display: "flex",
    justifyContent: "center"
  }, style || ({}))}>
      <style dangerouslySetInnerHTML={{
    __html: CSS
  }} />
      <div ref={ref} className={phoneCls} style={{
    transform: "scale(" + scale + ")"
  }}>
        <div className="xp-phone-body">
          {!hideNotch ? <div className="xp-notch" /> : null}
          <div className="xp-screen" style={screenStyle}>
            {wallpaper ? <img className="xp-wallpaper" src={wallpaper} alt="" /> : null}
            {walletBg ? <div className="xp-walletbg" /> : null}
            {children}
            {showClock ? <div className="xp-lockscreen">
                <div className="xp-lockscreen-date">{date}</div>
                <div className="xp-lockscreen-time">{time}</div>
              </div> : null}
            {!hideStatusBar ? <div className={statusCls}>
                <span className="xp-statusbar-time">{time}</span>
                <span className="xp-statusbar-icons">
                  <SignalIcon />
                  <WifiIcon />
                  <BatteryIcon />
                </span>
              </div> : null}
          </div>
          {!hideSideButton ? <div className="xp-sidebutton" /> : null}
        </div>
      </div>
    </div>;
};

## Tổng quan

<Steps>
  <Step title="Tạo mới khoá API">
    Khoá API dùng để đăng nhập máy quét.
  </Step>

  <Step title="Tạo máy quét">
    Xem chi tiết tại đây
  </Step>

  <Step title="Đăng nhập máy quét">
    <Columns cols={2}>
      <Column>
        * Vào trình duyệt trên điện thoại với địa chỉ website [https://scan.xanhcard.com/](https://scan.xanhcard.com/)
        * Sử dụng **Khoá API** và id của máy quét được tạo trước đó, điền vào các trường đăng nhập tương ứng.
        * Bấm **Add Scanner** để thêm scanner vào phần mềm
      </Column>

      <Column>
        <PhoneMockup>
          <div>
            <img src="https://mintcdn.com/xanhcard/WA-KKPhA1T-8iiHO/images/guides/scanner-login.png?fit=max&auto=format&n=WA-KKPhA1T-8iiHO&q=85&s=60c1ba4827a8b2b293057ccb3485ddf7" width="722" height="1562" data-path="images/guides/scanner-login.png" />
          </div>
        </PhoneMockup>
      </Column>
    </Columns>
  </Step>

  <Step title="Cấp quyền cho máy quét để sử dụng camera">
    <Columns cols={2}>
      <Column>
        <Warning>Điều này là cần thiết để máy quét có thể hoạt động bình thường được.</Warning>
      </Column>

      <Column>
        <PhoneMockup>
          <div>
            <img src="https://mintcdn.com/xanhcard/WA-KKPhA1T-8iiHO/images/guides/scanner-permission.png?fit=max&auto=format&n=WA-KKPhA1T-8iiHO&q=85&s=8567872fb4b3b3865c2a88542c23d329" width="722" height="1562" data-path="images/guides/scanner-permission.png" />
          </div>
        </PhoneMockup>
      </Column>
    </Columns>
  </Step>

  <Step title="Thực hiện quét mã QR trên thẻ khách hàng">
    <Columns cols={2}>
      <Column>
        * Sau khi đăng nhập máy quét thành công, bấm **Bắt đầu quét**.
        * Đưa thẻ khách hàng có chứa mã QR ra phía trước camera. Lúc này phần mềm sẽ tự nhận diện mã QR của khách hàng.
        * Xác nhận thông tin để thực hiện các hành động đã thiết lập sẵn trong máy quét.
      </Column>

      <Column>
        <PhoneMockup>
          <div>
            <img src="https://mintcdn.com/xanhcard/WA-KKPhA1T-8iiHO/images/guides/scanner-start.jpeg?fit=max&auto=format&n=WA-KKPhA1T-8iiHO&q=85&s=d346fdcd5ad24f2be5cefe3cb3b9e5cd" width="575" height="1192" data-path="images/guides/scanner-start.jpeg" />
          </div>
        </PhoneMockup>
      </Column>
    </Columns>
  </Step>

  <Step title="Nhận thông báo thành công hoặc thất bại">
    Sau khi xác nhận thông tin, trên máy quét sẽ hiển thị thông báo thành công hoặc lỗi xảy ra nếu có.
  </Step>
</Steps>

<Warning>
  Máy quét chạy trên trình duyệt của điện thoại, yêu cầu cần có kết nối mạng liên tục và có 1 camera hoạt động.
</Warning>

<Warning>
  Máy quét chỉ hoạt động tốt trên các trình duyệt như Chrome, Safari, Brave, Cốc Cốc.
</Warning>
