# 贩卖机硬件 API 对接文档 ## 基本信息 - 基础地址:`https://your-domain.com/api/vending` - 数据格式:JSON - 字符编码:UTF-8 - 所有请求需在 Header 中携带 `X-Machine-Id`(贩卖机唯一标识) ## 通用响应格式 ```json { "success": true, "data": {}, "message": "" } ``` | 字段 | 类型 | 说明 | |------|------|------| | success | boolean | 请求是否成功 | | data | object/null | 响应数据,失败时可能为 null | | message | string | 提示信息,失败时为错误原因 | --- ## 对接流程 ``` 用户打开 APP 展示会员二维码(含 token,5 分钟有效) │ ▼ 贩卖机扫描二维码,提取 token │ ▼ 调用【接口1】获取用户信息 │ ├── 返回 isLocked: true → 提示"该用户已在其他机器操作中" ├── 返回 isMember: false → 提示"非会员用户"(正常情况不会出现) └── 返回正常用户信息 + 优惠券列表 │ ▼ 用户选择商品,贩卖机根据优惠券信息判断是否使用优惠券 (每次只能使用一张,优先使用快到期、抵扣金额最大的) │ ▼ 用户完成支付(或取消/失败) │ ▼ 调用【接口2】上报支付结果(无论成功或失败都必须调用) ``` --- ## 接口1:获取用户信息 贩卖机扫描用户二维码后,通过此接口获取用户会员信息和可用优惠券列表。 调用成功后,系统会自动锁定该用户(防止同时在多台机器操作),并使二维码失效(防止截图盗刷)。 ### 请求 ``` POST /api/vending/user-info ``` **Headers:** | 名称 | 必填 | 说明 | |------|------|------| | Content-Type | 是 | application/json | | X-Machine-Id | 是 | 贩卖机唯一标识 | **Body:** ```json { "qrcodeToken": "用户二维码中包含的 token 字符串" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | qrcodeToken | string | 是 | 从用户二维码中解析出的 token | ### 响应 **成功(正常用户):** ```json { "success": true, "data": { "userId": "user_abc123", "isMember": true, "isLocked": false, "coupons": [ { "couponId": "coupon_001", "type": "thresholddiscount", "thresholdAmount": 100.00, "discountAmount": 5.00, "expireAt": "2026-04-30T23:59:59Z" }, { "couponId": "coupon_002", "type": "directdiscount", "thresholdAmount": null, "discountAmount": 3.00, "expireAt": "2026-05-15T23:59:59Z" } ] }, "message": "" } ``` **成功(用户已被其他机器锁定):** ```json { "success": true, "data": { "userId": "user_abc123", "isMember": true, "isLocked": true, "coupons": [] }, "message": "" } ``` ### 响应字段说明 | 字段 | 类型 | 说明 | |------|------|------| | userId | string | 用户唯一标识 | | isMember | boolean | 是否为会员 | | isLocked | boolean | 是否已被其他贩卖机锁定。为 true 时应提示用户"已在其他机器操作中" | | coupons | array | 用户可用优惠券列表(已按规则排序) | **优惠券字段说明:** | 字段 | 类型 | 说明 | |------|------|------| | couponId | string | 优惠券 ID,支付回调时需回传 | | type | string | 优惠券类型:`thresholddiscount`(满减券)、`directdiscount`(抵扣券) | | thresholdAmount | decimal/null | 满减门槛金额。满减券时有值,抵扣券时为 null | | discountAmount | decimal | 抵扣金额 | | expireAt | string | 到期时间(UTC,ISO 8601 格式) | **优惠券排序规则(已由服务端排好序):** 1. 到期时间升序(快到期的排前面) 2. 到期时间相同时,抵扣金额降序(金额大的排前面) **优惠券使用规则:** - 每次交易只能使用一张优惠券 - 满减券:用户消费金额 ≥ thresholdAmount 时可使用 - 抵扣券:无门槛,直接抵扣 - 建议优先使用列表中排在前面的优惠券(已按最优顺序排列) ### 错误响应 | 场景 | success | message | |------|---------|---------| | 二维码无效或已过期 | false | 二维码无效或已过期 | | 用户不存在 | false | 用户不存在 | --- ## 接口2:支付结果回调 用户在贩卖机完成支付后(无论成功、失败或取消),贩卖机必须调用此接口上报支付结果。 系统会根据支付结果: - 支付成功:发放积分、核销优惠券、解除用户锁定 - 支付失败/取消:仅解除用户锁定 **重要:无论支付结果如何,都必须调用此接口,否则用户将保持锁定状态(10 分钟后自动超时解锁)。** ### 请求 ``` POST /api/vending/payment-callback ``` **Headers:** | 名称 | 必填 | 说明 | |------|------|------| | Content-Type | 是 | application/json | | X-Machine-Id | 是 | 贩卖机唯一标识 | **Body:** ```json { "userId": "user_abc123", "machineId": "machine_001", "paymentAmount": 50.00, "usedCouponId": "coupon_001", "paymentStatus": "success", "transactionId": "txn_20260408_001" } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | userId | string | 是 | 用户 ID(从接口1获取) | | machineId | string | 是 | 贩卖机 ID | | paymentAmount | decimal | 是 | 用户实际支付金额(优惠券抵扣后的金额) | | usedCouponId | string | 否 | 使用的优惠券 ID,未使用时传 null 或不传 | | paymentStatus | string | 是 | 支付状态:`success`(成功)、`failed`(失败)、`cancelled`(取消) | | transactionId | string | 是 | 贩卖机端的交易流水号(用于对账) | ### 响应 **成功:** ```json { "success": true, "data": null, "message": "支付回调处理成功" } ``` ### 错误响应 | 场景 | success | message | |------|---------|---------| | 用户不存在 | false | 用户不存在 | | 处理异常 | false | 支付回调处理失败 | --- ## 附录 ### 用户锁定机制 - 调用接口1成功获取用户信息后,系统自动锁定该用户 - 锁定期间,其他贩卖机扫同一用户的二维码,接口1会返回 `isLocked: true` - 调用接口2后,系统自动解除锁定 - 安全兜底:锁定 10 分钟后自动超时解锁(防止贩卖机异常未调用接口2) ### 二维码说明 - 二维码由用户 APP 端生成,有效期 5 分钟 - 二维码内容为一个 token 字符串,贩卖机扫码后提取该 token 调用接口1 - 每个二维码只能使用一次,扫码成功后立即失效 - 用户可重新生成新的二维码 ### 积分发放规则 - 仅会员用户支付成功时发放积分 - 积分 = 支付金额 × 转换比(后台可配置,默认 1 元 = 1 积分) - 积分发放由系统在接口2处理时自动完成,无需贩卖机额外操作