本文档主要涉及开发指引中对停车入场场景的处理流程,希望通过对微信支付分停车服务停车相关API的接入和开发流程的讲解,能帮助商户开发人员顺利完成对停车入场的接入和开发。
在阅读本开发指引前,请确保您已经:
在对创建停车入场API发起请求前,商户后台系统需要生成对于该车辆的商户侧入场标识ID(即 out_parking_no
参数),商户后台系统需要保证每个 out_parking_no
对应唯一的停车操作(为对商户侧提供更高的灵活性,微信支付分停车服务将根据商户侧入场标识ID唯一定位到某次停车操作,而不会通过车牌号对停车操作进行限制。因此,若商户后台系统对同一车辆绑定不同的 out_parking_no
重复调用创建停车入场API,将导致重复创建停车入场)。
商户后台系统可以根据车牌号获取该车辆的停车状态,若(一段时间内)已经入场则拒绝该停车请求,避免停车场重复发送请求导致用户重复入场。接着,商户后台系统可以保存该车辆的停车信息,并为之生成唯一的入场标识ID。
// 判断是否已经停车,若已停车则直接返回错误
if dao.GetParkingStatus(params.PlateNumber) {
err := errors.Errorf("Vehicle %s is already parking now", params.PlateNumber)
log.Print(err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// 生成商户方的停车 ID
// 保存用户停车入场信息、生成商户侧入场标识 ID 的具体实现需要商户方根据现实情况进行处理
outParkingNo := util.GetRandIntStr()
根据车辆信息(如车牌号、车牌颜色)、停车场相关信息(如停车场名称、停车免费时长)和停车开始时间等,结合商户侧的入场标识ID,商户后台系统就可以开始对创建停车入场API发起请求了。除此之外,商户后台系统还需要提供回调URL参数,用于接受微信支付分停车服务对停车期间用户开通或关闭服务的通知。
// 构造并发起创建停车请求
req := model.CreateParkingRequest{
PlateNumber: params.PlateNumber,
PlateColor: params.PlateColor,
ParkingName: params.ParkingName,
StartTime: params.StartTime,
FreeDuration: params.FreeDuration,
NotifyURL: fmt.Sprintf("%s/service", os.Getenv("DOMAIN")),
OutParkingNo: outParkingNo,
}
reqBody, err := json.Marshal(&req)
if err != nil {
err = errors.Errorf("Fail to marshal request: %v", req)
log.Print(err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Printf("Req: %s", string(reqBody))
apiResult, err := client.GetClient().Post(
r.Context(),
"https://api.mch.weixin.qq.com/v3/vehicle/parking/parkings",
reqBody,
)
if err != nil {
err = errors.Errorf("Fail to create parking service: %v", err)
log.Print(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
接着商户后台系统需要读取并解析 apiResult
中的响应体,并根据响应体中用户支付分停车服务状态进行相应的处理。
微信支付分停车服务会检查用户车牌的服务开通情况、欠款状态等基本信息,并查询用户是否符合微信支付分停车条件,最终返回该车牌相应的服务状态和停车ID。当用户创建停车入场成功时,商户后台系统需要更新用户的停车状态(如设置用户停车方式为微信支付分停车),并设置微信支付分服务侧的停车ID;否则设置用户停车方式为传统方式停车,并进行相关状态和信息的更新,同时返回相应的错误码和错误信息给停车场系统。
respBody, err := ioutil.ReadAll(apiResult.Response.Body)
if err != nil {
err = errors.Errorf("Fail to read from response body: %v", err)
log.Print(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Resp: %s", string(respBody))
resp := model.CreateParkingResponse{}
if err = json.Unmarshal(respBody, &resp); err != nil {
err = errors.Errorf("Fail to unmarshal from response body: %v", err)
log.Print(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 判断用户服务状态
if resp.State == "NORMAL" { // nolint:goconst
// 更新用户停车信息,入场成功
dao.SaveServiceStatus(params.PlateNumber, true)
dao.SaveParkingID(params.PlateNumber, resp.ParkingID)
dao.SaveOutParkingNo(params.PlateNumber, outParkingNo)
} else {
// 更新用户停车信息,入场失败
dao.SaveServiceStatus(params.PlateNumber, false)
err = errors.Errorf("Vehicle is blocked for reason: %v", resp.BlockReason)
log.Print(err.Error())
util.WriteError(w, http.StatusForbidden, error.Error{
Code: error.PermissionDenied,
Message: err.Error(),
Details: struct {
Reason string `json:"reason"`
}{
Reason: resp.BlockReason,
},
})
}
用户在停车期间可能会开通或关闭微信支付分停车服务,这时微信支付分停车服务会向用户停车入场时商户提供的回调URL发起停车入场状态变更通知请求。商户后台系统需要接收回调并更新用户的微信支付分停车服务状态。
为了确保回调请求由微信支付发送,微信支付会在回调请求的HTTP头部包含回调报文的签名,商户方需要对其进行验证。在微信支付Go SDK的 core/notify
包中提供了回调通知处理器 Handler
,用于回调通知的验签与解密工作(具体参考微信支付 API v3 Go SDK)。开发者可以通过回调通知处理器直接解析回调请求,并获取请求数据。
req := model.ServiceStatusNotifyRequest{}
if _, err := client.GetNotifyHandler().ParseNotifyRequest(r.Context(), r, &req); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
log.Printf("Req: %v", util.ToString(req))
// 更新用户服务状态
if req.ParkingState == "NORMAL" { // nolint:goconst
dao.SaveServiceStatus(req.PlateNumber, true)
} else {
dao.SaveServiceStatus(req.PlateNumber, false)
}