Bước 1: Truy cập URL https://mini.zalo.me/developers và chọn Mini App -> Hiện thị giao diện Tổng quan về Mini App của bạn.
Bước 2: Ở menu chọn Checkout SDK -> Chọn Cấu hình chung
Cập nhật AppStatus thành ACTIVE.
Lưu PrivateKey vào trong file app-config.json:
"app": { "title": "Demo", "textColor": "black", "leftButton": "none", "statusBar": "transparent", "selfControlLoading": false, "hideAndroidBottomNavigationBar": false, "hideIOSSafeAreaBottom": false, "actionBarHidden": true, "privateKey": "238c28a798ba53f63e89b52fc2f40340" }, |
Bước 3: Thêm phương thức thanh toán Alepay
Trong mục CheckoutSDK phần Phương thức thanh toán ấn Thanh toán thêm mới.
Hiển thị giao diện Thêm thanh toán mới:
Nhập hợp lệ các trường thông tin:
Chọn Phương thức thanh toán là Phương thức thanh toán khác
Mã phương thức:
Sandbox: ALEPAY_SANDBOX
Live: ALEPAY
Tên phương thức: Alepay: ATM/QRCode, Thẻ Quốc Tế, Trả góp,APay, GPay
Ảnh đại diện phương thức thanh toán lấy logo của Alepay
Bước 4: Cài đặt Zalo Mini App SDK
Trong dự án (ví dụ: zaui-coffee), đảm bảo đã cài đặt ZMP SDK và Checkout SDK.
Link hướng dẫn cài đặt: https://mini.zalo.me/documents/devtools/
Lấy key kết nối Alepay:
Đăng nhập vào tài khoản bạn đã đăng ký trên Alepay.vn
Vào mục Tài khoản chọn Cài đặt tài khoản -> Key tích hợp và copy thông tin TokenKey và ChecksumKey.
Lưu các thông tin key kết nối vào file app-config.json:
"alepay": { "method": "CUSTOM_ALEPAY_SANDBOX", "apiKey": "jWglwAv5WA5B8gguVsHxyitoPBEcco", "checksumKey": "wmN5tsrMgXAjALCzUPP2osp7PmjVRG", "apiUrl": "https://alepay-v3-sandbox.nganluong.vn" } |
Cài đặt package axios để gọi API
npm install axios
npm install-save-dev @types/crypto-js
Bước 1: Hiển thị phương thức thanh toán Alepay
Đối tác sử dụng API selectPaymentMethod để gửi thông tin phương thức thanh toán muốn hiển thị. Checkout SDK sẽ hiển thị giao diện lựa chọn phương thức cho người dùng lựa chọn và trả kết quả cho đối tác.
Sửa file src/pages/cart/delivery.tsx để xử lý nút chọn phương thức thanh toán.
const setPaymentMethod = useSetRecoilState(selectedPaymentMethodState); const [methodName, setMethodName] = useState(""); const selectMethod = () => { selectPaymentMethod({ channels: [{ method: window.APP_CONFIG.alepay.method }], success: (data) => { // Lựa chọn phương thức thành công const { method, isCustom, logo, displayName, subMethod } = data; setMethodName(displayName || ""); setPaymentMethod(method); }, fail: (err) => { console.log(err); }, }); }; |
{ left: <Icon icon="zi-qrline" className="my-auto" />, right: (<ListItem onClick="{selectMethod}" title="Chọn phương thức thanh toán" subtitle="{methodName}" />), }, |
Bước 2: Khởi tạo đơn hàng
Đối tác sử dụng API purchase để khởi tạo đơn hàng.
Sửa file src/pages/cart/preview.tsx để xử lý nút chọn phương thức thanh toán.
const quantity = useRecoilValue(totalQuantityState); const totalPrice = useRecoilValue(totalPriceState); const selectedPaymentMethod = useRecoilValue(selectedPaymentMethodState); const [isLoading, setLoading] = useState(false); const pay = async () => await purchase({ desc: `Thanh toán cho ${getConfig((config) => config.app.title)}`, amount: totalPrice, method: selectedPaymentMethod, success: (data) => { const { orderId, messageToken } = data; const returnUrl = `https://zalo.me/s/${window.APP_ID}/?orderId=${orderId}`; setLoading(true); const alepayData = { tokenKey: window.APP_CONFIG.alepay.apiKey, orderCode: orderId, customMerchantId: null, amount: totalPrice, currency: "VND", orderDescription: "Thanh toán cho đơn hàng " + orderId, totalItem: 1, installment: false, checkoutType: 4, month: null, bankCode: null, paymentMethod: null, returnUrl: returnUrl, cancelUrl: returnUrl, buyerName: "Nguyen Van", buyerEmail: "testalepay@yopmail.com", buyerPhone: "0912880705", buyerAddress: "18 Tam Trinh", buyerCity: "Hà Nội", buyerCountry: "Việt Nam", paymentHours: 48, promotionCode: null, merchantSideUserId: null, buyerPostalCode: null, buyerState: null, isCardLink: false, allowDomestic: true, cashback: null, discount: null, signature: "", }; const newData = alepayData; Reflect.deleteProperty(newData, "signature"); const dataMac = Object.keys(newData) .sort() .map((key) => { if (newData[key] === null) { return `${key}=`; } return `${key}=${newData[key]}`; }) .join("&"); const checksum = CryptoJS.HmacSHA256( dataMac, window.APP_CONFIG.alepay.checksumKey ).toString(CryptoJS.enc.Hex); alepayData.signature = checksum; axios .post( `${window.APP_CONFIG.alepay.apiUrl}/api/v3/checkout/request-payment`, alepayData, { headers: { "Content-Type": "application/json" }, } ) .then((response) => { const { data } = response; if (data && data.code === "000") { openWebview({ url: data.checkoutUrl, config: { style: "bottomSheet", leftButton: "back", }, success: (res) => {}, fail: (error) => {}, }); } else { alert("Payment error: " + JSON.stringify(data)); } }) .catch((error) => alert(JSON.stringify(error))); }, fail: (err) => { alert("Payment error: " + JSON.stringify(err)); }, }); |
Bước 3: Xử lý kết quả thanh toán phía Server
Khi hoàn tất quá trình thanh toán, đối tác cần cập nhật kết quả cho Checkout SDK qua API updateOrderStatus.
Sửa file src/hooks.ts để xử lý sự kiện sau khi thanh toán trên Alepay quay trở về Mini App.
events.on(EventName.OpenApp, (data) => { if (data?.path) { const path = data.path.replace("/?", ""); const searchParams = new URLSearchParams(path); const orderId = searchParams.get("orderId"); const errorCode = searchParams.get("errorCode"); let resultCode = -1; if (errorCode === "000") { resultCode = 1; } if (orderId) { const dataMac = `appId=${window.APP_ID}&orderId=${orderId}&resultCode=${resultCode}&privateKey=${window.APP_CONFIG.app.privateKey}`; const hmac = CryptoJS.HmacSHA256(dataMac, window.APP_CONFIG.app.privateKey).toString(CryptoJS.enc.Hex); let body = { appId: window.APP_ID, orderId: orderId, resultCode: resultCode, mac: hmac, }; axios .post("https://payment-mini.zalo.me/api/transaction/" + window.APP_ID + "/custom-callback-payment", body, { headers: { "Content-Type": "application/json" } }) .then((response) => { navigate("/result", { state: data }); }) .catch((error) => alert(JSON.stringify(error))); } else { navigate(data?.path, { state: data, }); } } }); |
Sau khi cập nhật tình trạng thanh toán của đơn hàng thì điều hướng sang trang hiển thị kết quả giao dịch.
Sửa file src/pages/result.tsx.
interface PaymentResult { transId: string; method: string; returnCode: number; returnMessage: string; isProcessing: boolean; amount: number; transTime: number; merchantTransId: string; extradata: string; subResultCode: number; updateAt: number; createAt: number; } const navigate = useNavigate(); const { state } = useLocation(); const [paymentResult, setPaymentResult] = useState < PaymentResult | undefined > (); useEffect(() => { let timeout; const check = () => { let data = state; const path = data.path.replace("/?", ""); const searchParams = new URLSearchParams(path); const orderId = searchParams.get("orderId"); const dataMac = `appId=${window.APP_ID}&orderId=${orderId}&privateKey=${window.APP_CONFIG.app.privateKey}`; const hmac = CryptoJS.HmacSHA256(dataMac, window.APP_CONFIG.app.privateKey).toString(CryptoJS.enc.Hex); axios .get("https://payment-mini.zalo.me/api/transaction/get-status?" + `appId=${window.APP_ID}&orderId=${orderId}&mac=${hmac}`, { headers: { "Content-Type": "application/json" }, }) .then((response) => { const { data } = response; setPaymentResult(data.data); if (data.data.returnCode === undefined || data.data.returnCode === 0) { timeout = setTimeout(check, 3000); } }) .catch((error) => setPaymentResult(error)); }; check(); return () => { clearTimeout(timeout); }; }, []); const clearCart = useResetRecoilState(cartState); useEffect(() => { if (paymentResult && paymentResult.returnCode !== undefined && paymentResult.returnCode >= 0) { clearCart(); } }, [paymentResult]); |
return ( <Page className="flex flex-col bg-white"> <Header title="Kết quả thanh toán" /> {(function (render: (result: RenderResultProps) => ReactNode) { if (paymentResult && paymentResult.returnCode) { if (paymentResult.returnCode === 1) { return render({ title: "Thanh toán thành công", message: `Đơn hàng của bạn đã được thanh toán thành công!`, icon: <IconPaymentSuccess />, }); } else { return render({ title: "Thanh toán thất bại!", message: "", icon: <IconPaymentFail />, }); } } return render({ message: `Hệ thống đang xử lý thanh toán, vui lòng chờ trong ít phút...`, icon: <IconPaymentLoading />, }); })(({ title, message, icon }: RenderResultProps) => ( <Box className="p-6 space-y-3 flex-1 flex flex-col justify-center items-center text-center"> <div className="p-4">{icon}</div> {title && ( <Text size="xLarge" className="font-medium"> {title} </Text> )} <Text className="text-[#6F7071]">{message}</Text> </Box> ))} {paymentResult && ( <div className="p-4"> <Button fullWidth onClick={() => navigate("/", { replace: true })}> {paymentResult.returnCode === 1 ? "Hoàn tất" : "Đóng"} </Button> </div> )} </Page> ); |