支付宝-NodeJs面支付(扫码支付)

本文章适合初学者自主学习研究,欢迎大家互相学习提问。如有书写说明错误请及时联系我加以修正。谢谢~

一、准备工作

产品介绍

1.手机支付宝搜索->产品签约->签约组手->申请并开通面付款功能(无需营业执照)

ps:其他功能的开通需要申请营业执照

官网地址:小程序文档 - 支付宝文档中心

 2.电脑网页打开:支付宝开放平台

 完成接入并访问后进入“控制台”会看到当面付应用已开启:

3.点开应用完成(秘钥/证书)的设置

获取秘钥

1.官方 秘钥生成工具:小程序文档 - 支付宝文档中心

2. 生成私钥和公钥文件

3. 继续完成前部分步骤3的秘钥配置

沙箱测试环境

支付测试工具

完成沙箱测试环境秘钥的配置并启用(也可以使用系统默认秘钥)

 

安装沙箱测试支付宝App

账号密码

二、后端支付接口

 框架技术:NodeJs + Express + TS + MySql

秘钥文件

创建文件存储“应用私钥”和“支付宝公钥”(在步骤5中获取)

配置SDK 

1.安装支付宝支付SDK:npm install alipay-sdk --save

npm文档地址:alipay-sdk - npm

2.初始化SDK

创建alipay.ts文件

目录结构
--------------
-utils
 -alipay.ts
---------------

/**
 * 对接支付宝 H5 支付
 */
import AlipaySdk from 'alipay-sdk' // 引入SDK
import { APP_PRIVATE_KEY, ALIPAY_PUBLIC_KEY } from '../config/payment-key'

export const alipaySdk = new AlipaySdk({
   appId: '9021000123611250',
   keyType: 'PKCS8', // node版本需要配置这个,否则会出现支付地址获取不到等问题
   signType: 'RSA2', // 签名算法,默认 RSA2
   gateway: 'https://openapi-sandbox.dl.alipaydev.com/gateway.do', // 支付宝网关地址 ,沙箱环境下使用时需要修改
   alipayPublicKey: ALIPAY_PUBLIC_KEY, // 支付宝公钥
   privateKey: APP_PRIVATE_KEY // 应用私钥
})

3.创建路由引入SDK完成支付下单

SDK官方文档:小程序文档 - 支付宝文档中心

创建订单接口
import express from 'express'
import { v4 as uuidv4 } from 'uuid'
import axios from 'axios' // 用于调用支付接口

// 创建注册页面路由
const payment = express.Router()

// 引入支付宝SDK
import { alipaySdk } from '../utils/alipay'

// 发起支付宝的当面付
payment.post('/inPerson', checkParamsIsNull, checkToken, (req, res) => {
   const { totalAmount, ... } = req.body
   const outTradeNo = uuidv4() // 订单号
   // 填写订单基础信息
   const bizContent = {
      out_trade_no: outTradeNo, // 商户订单号,64个字符以内、可包含字母、数字、下划线,且不能重复
      total_amount: totalAmount, // 订单总金额('0.01'),单位为元,精确到小数点后两位[string]类型
      subject: '积分充值', // 订单标题
   }
   // 获取支付宝官方支付链接
   const resultUrl = alipaySdk.pageExec('alipay.trade.precreate', {
      method: 'GET',
      bizContent
   })
   // 生成订单
   axios.get(resultUrl).then(resultData => {
      // 给前端发送订单信息
      ...
      res.json({ ...resultData.data, msg: '订单创建成功' })
   }).catch(err => {
      res.json({ msg: '订单创建失败', err })
   })
})

export default payment
 解析订单接口
...
// 解析支付订单状态
payment.post('/queryOrder', checkParamsIsNull, checkToken, async (req, res) => {
   const { outTradeNo, ... } = req.body
   const bizContent = { outTradeNo }
   const resultData = alipaySdk.pageExec('alipay.trade.query', {
      method: 'GET',
      bizContent
   })
   try {
      // 查询订单结果
      const queryRes = await axios.get(resultData)
      const queryData = queryRes.data.alipay_trade_query_response
      const { code, trade_status } = queryData
      if (code === '10000') {
         switch (trade_status) {
            case 'WAIT_BUYER_PAY':
               res.json({ msg: '交易创建,等待买家付款', code, status: trade_status })
               break
            case 'TRADE_CLOSED':
               res.json({ msg: '未付款交易超时关闭,或支付完成后全额退款', code, status: trade_status })
               break
            case 'TRADE_SUCCESS':
               // 支付成功的操作
               ...
               res.json({ msg: '交易支付成功', queryData, code, status: trade_status })
               break
            case 'TRADE_FINISHED':
               res.json({ msg: '交易结束,不可退款', code, status: trade_status })
               break
            default: res.end();
         }
      } else if (code === '40004') {
         res.json({ msg: '等待支付', code })
      } else {
         res.json({ msg: '请根据文档:https://opendoc.alipay.com/common/02km9f中的code码查阅相关信息!', code })
      }
   } catch (err) {
      res.json({ msg: '查询失败', err })
   }
})
...
路由中间件

在真正进入路由前的操作(选填),如参数非空验证、Token验证...

import jwt from 'jsonwebtoken'
import { TOKEN_KEY } from '../config'

/*** 校验为空的参数[中间件] */
export const checkParamsIsNull = (req: any, res: any, next: Function) => {
   const method = req.method.toUpperCase()
   const params = method === 'GET' ? req.query : req.body
   if (JSON.stringify(params) === '{}') return res.status(400).json({ msg: '未传递任何参数,请确认传递的方式是否正确!', code: 0 })
   const keys = []
   for (let key in params) {
      if (!params[key]) keys.push(key)
   }
   if (keys.length > 0) return res.status(400).json({ msg: `传递的${keys.join('-')}为空`, code: 0 })
   next()
}

/*** Token校验[中间件] */
export const checkToken = (req: any, res: any, next: Function) => {
   const token = req.headers.authorization.split(' ')[1]
   jwt.verify(token, TOKEN_KEY, (err: any, decoded: any) => {
      if (err) {
         // Token 校验失败
         return res.status(401).json({ message: '无效的token', code: 0 })
      }
      // Token 验证通过
      req.userId = decoded.userId // 将解码后的用户ID存储在请求中,以便后续处理使用
      next()
   })
} 

三、前端调用支付接口

以uniapp框架为例

 发起请求的API封装

api目录下的 pay.ts 文件

// 创建订单的参数类型
interface createOrderType {
   totalAmount: string // 订单金额
   ...
}
/**
 * 创建订单
 * @returns Promise
 */
export const createOrderApi = (params: createOrderType) => {
   return uni.request({
      url: '/api/payment/inPerson', // '/api'前端代理接口(解决跨域)
      method: 'POST',
      header: { // 请求头设置Token(选填)
         'authorization': `Bearer ${uni.getStorageSync('TOKEN')}`
      },
      data: params
   })
}

// 订单查询参数类型
interface queryOrderType {
   outTradeNo: string // 订单号
   ...
}
/**
 * 解析订单是否支付成功
 * @param outTradeNo 订单号
 * ...
 * @returns Promise
 */
export const queryPayOrderApi = (params: queryOrderType) => {
   return uni.request({
      url: '/api/payment/queryOrder',
      method: 'POST',
      header: { // 请求头设置Token(选填)
         'authorization': `Bearer ${uni.getStorageSync('TOKEN')}`
      },
      data: params
   })
}
调用创建支付接口
// 创建订单
const createPayOrde = () => {
   ...
   createOrderApi({
      totalAmount: totalAmount.value,
      ...
   }).then((res: any) => {
      const { alipay_trade_precreate_response: r } = res.data
      if (r.code === '10000') {
         const ordeInfo = {
            total_amount: totalAmount.value, // 总金额
            qr_code: r.qr_code, // 支付二维码
            out_trade_no: r.out_trade_no, // 订单号
            ...
         }
         ...
      } else {
         uni.hideLoading()
         uni.showToast({ title: '订单创建失败', icon: 'error' })
      }
   }).catch(res => {
      console.log(res.data.msg)
   })
}
调用查询支付结果接口
const noticeTxt = ref<string>('请扫码完成支付') // 文本提示
const isTimeout = ref<boolean>(false) // 是否支付超时
const payStatus = ref<boolean>(false) // 是否支付完成
let payTimer // 支付定时器

...

// 判断支付是否完成
const paymentListen = () => {
   if (payStatus.value) return noticeTxt.value = '该订单已完成'
   payTimer = setTimeout(() => {
      clearTimeout(payTimer)
      if (isTimeout.value) return
      queryPayOrderApi({outTradeNo, ...}).then(async (queryRes: any) => {
         // console.log(queryRes.data)
         const { code, status } = queryRes.data
         if (code === '10000') {
            switch (status) {
               case 'TRADE_SUCCESS': noticeTxt.value = '支付成功!'
                  // 支付成功的处理程序
                  ...
                  break
               default:
                  noticeTxt.value = '支付中...'
                  paymentListen()
                  break
            }
         } else {
            noticeTxt.value = '请扫描二维码完成支付'
            paymentListen()
         }
      })
   }, 1500)
}
效果图

四、生产环境

替换测试环境 alipay.ts 文件中的"appid"和"gateway" 与面付款应用保持一致

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值