一、前置条件
(1)go语言,1.18
(2)Gin、第三方依赖包:gopay【github.com/go-pay/gopay/alipay】https://github.com/go-pay/gopay/blob/main/doc/wechat_v3.md
(3)支付宝支付相关信息:appID ,支付宝用户私钥,支付宝公钥【小程序需要拉起支付宝APP,不能使用沙盒账号】小程序文档 - 支付宝文档中心
(4)外网可访问的域名,用于支付成功回调;本地开发自测的话,搞个内网穿透工具;我用的花生壳,花了6块钱买了个域名【当时搞活动买1年,送98年】
(5)基于域名创建映射
(6)启动本地服务,验证映射是否成功
准备工作完成,开始编码!!
二、支付客户端代码:aliPayClient
(1)保证单列,用go的sync.Once实现,具体代码如下
package lib
import (
"github.com/go-pay/gopay/alipay"
"log"
. "go_online_pay/config"
"sync"
)
var aliPayClient *alipay.Client
var syOnce sync.Once
//GetAliPayClient aliPayClient
func GetAliPayClient() *alipay.Client {
var err error
syOnce.Do(func() {
cf := Conf{}
config := cf.GetConf()
aliPayClient, err = alipay.NewClient(config.AliPayAppId, config.AliPayPrivateKey, config.AliPayIsProd)
if err != nil {
log.Panic(err.Error())
}
//配置公共参数
aliPayClient.SetCharset("utf-8").
SetSignType(alipay.RSA2).
SetNotifyUrl("")
})
return aliPayClient
}
(2)基础配置信息,都放到conf.yaml文件内了,同时配套conf.go文件,mian.go启动加载conf.ymal配置内容,后续通过conf.Xx获取属性值【config.AliPayAppId】,根据自己项目的实际情况,放置支付宝相关配置信息
#--------------支付宝支付测试账号(可真实支付)----------------
#是否生产账号
alipay_is_prod: true
#支付宝-APPID
alipay_app_id:
#支付宝-用户私钥
alipay_private_key:
#支付宝公钥
alipay_public_key:
#支付宝支付回调地址
alipay_notify_url: https://XXX/alipayNotify
三、支付服务类代码:AlipayService
(1)代码中含有:面对面(收款码链接),小程序支付,查询支付结果,退款,查询退款结果
package service
import (
"context"
"fmt"
"github.com/go-pay/gopay"
"github.com/go-pay/gopay/alipay"
"go_online_pay/config"
"go_online_pay/lib"
)
type AlipayService struct {
}
//TradePreCreate 当面付 扫码支付,获取二维码
func (AlipayService) TradePreCreate(bm gopay.BodyMap) string {
client := lib.GetAliPayClient()
client.SetNotifyUrl(config.GetConfLib().AliPayNotifyUrl)
//统一收单线下交易预创建
payParam, err := client.TradePrecreate(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----aliPay TradePreCreate()-------error:%s", err.Error()))
return ""
}
return payParam.Response.QrCode
}
//TradeCreate 统一收单交易创建接口
func (AlipayService) TradeCreate(bm gopay.BodyMap) (*alipay.TradeCreateResponse, error) {
client := lib.GetAliPayClient()
//统一收单线下交易预创建
payParam, err := client.TradeCreate(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----aliPay TradeCreate()-------error:%s", err.Error()))
return nil, err
}
return payParam, nil
}
//Refund 退款接口
func (AlipayService) Refund(bm gopay.BodyMap) *alipay.TradeRefundResponse {
client := lib.GetAliPayClient()
//统一收单线下交易预创建
payParam, err := client.TradeRefund(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----aliPay Refund()-------error:%s", err.Error()))
return nil
}
//接口返回fund_change=Y为退款成功,fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态
return payParam
}
//QueryRefund 查询退款接口
func (AlipayService) QueryRefund(orderNo, refundNo string) (*alipay.TradeFastpayRefundQueryResponse, error) {
client := lib.GetAliPayClient()
//统一收单线下交易预创建
bm := make(gopay.BodyMap)
//商户订单号
bm.Set("out_trade_no", orderNo)
//退款请求号
bm.Set("out_request_no", refundNo)
payParam, err := client.TradeFastPayRefundQuery(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----aliPay QueryRefund()-------error:%s", err.Error()))
}
//接口返回fund_change=Y为退款成功,fund_change=N或无此字段值返回时需通过退款查询接口进一步确认退款状态
return payParam, err
}
//TradeQuery 交易查询接口
func (AlipayService) TradeQuery(orderNo string) *alipay.TradeQueryResponse {
client := lib.GetAliPayClient()
//请求参数
bm := make(gopay.BodyMap)
bm.Set("out_trade_no", orderNo)
//统一收单线下交易预创建
payParam, err := client.TradeQuery(context.Background(), bm)
if err != nil {
fmt.Println(fmt.Sprintf("-----aliPay TradeQuery(%s)-------error:%s", orderNo, err.Error()))
return nil
}
return payParam
}
四、具体方法使用
(1)小程序支付部分代码
//第三方30分钟过期
expire := time.Now().Add(30 * 60 * time.Second).Format(time.RFC3339)
description := util.ExtractStrByLen(orderInfo.CommodityName, 40)
//请求参数
bm := make(gopay.BodyMap)
bm.Set("notify_url", config.GlobalConf.AliPayNotifyUrl)
bm.Set("subject", description).
//买家支付宝用户ID
Set("buyer_id", openId).
//商户订单号。由商家自定义
Set("out_trade_no", payNo).
//元,小数点两位,去掉千分位
Set("total_amount", strings.Replace(payAmount, ",", "", -1))
//统一收单线下交易预创建
payParam, errAli := AlipayService{}.TradeCreate(bm)
if errAli != nil {
fmt.Println(fmt.Sprintf("-----aliPay TradeCreate() 返回值 ---errString -> %s", errAli.Error()))
errMap := util.JsonToMap(errAli.Error())
if val, ok := errMap["code"]; ok {
result["errorMsg"] = "拉起支付宝支付失败:" + val.(string)
return e.ERROR, result
}
}
if payParam == nil || len(payParam.Response.TradeNo) == 0 {
result["errorMsg"] = "拉起支付宝支付失败:无AliPrepayId"
return e.ERROR, result
}
result["ali_prepay_Id"] = payParam.Response.TradeNo
前端拿到ali_prepay_Id后,通过sdk拉起支付宝APP
(2) 其他方法,都可以仿照小程序入参方式,根据支付宝接口文档填写相应的key即可
(3)支付回调方法代码:回调后写入队列,异步处理
//AlipayNotify 解析支付宝支付异步通知的参数到BodyMap
func AlipayNotify(c *gin.Context) {
logger.Info("---------------------AlipayNotify START -------------")
// c.Request 是 gin 框架的写法
notifyReq, err := alipay.ParseNotifyToBodyMap(c.Request)
if err != nil {
logger.Info("------AlipayNotify ERR -------------,err:", err.Error())
return
}
logger.Info("-----AlipayNotify notifyReq: ", notifyReq)
ok, err := alipay.VerifySign(config.GlobalConf.AliPayPublicKey, notifyReq)
if ok {
if notifyReq["receipt_amount"] == nil {
logger.Info("------ AlipayNotify 支付宝退款回调,忽略 -------------")
} else {
//验签成功
var alReq = make(map[string]interface{})
//商户订单号:商户系统内部订单号
alReq["pay_no"] = notifyReq["out_trade_no"]
alReq["trade_no"] = notifyReq["trade_no"]
//与支付宝同步
alReq["trade_status"] = notifyReq["trade_status"]
alReq["notify_time"] = notifyReq["notify_time"]
//订单金额。本次交易支付的订单金额,单位为人民币(元)
totalAmountF, _ := strconv.ParseFloat(notifyReq["total_amount"].(string), 64)
totalAmount := int(totalAmountF * 100)
//实收金额。商家在交易中实际收到的款项,单位为人民币(元)。
receiptAmountF, _ := strconv.ParseFloat(notifyReq["receipt_amount"].(string), 64)
receiptAmount := int(receiptAmountF * 100)
alReq["total_amount"] = totalAmount
alReq["receipt_amount"] = receiptAmount
var mapData = make(map[string]interface{})
mapData["data_type"] = "PayNotify"
mapData["param"] = map[string]interface{}{"payType": "aliPay", "notifyReq": alReq}
reqJson, _ := json.Marshal(mapData)
lib.PushMessToPayQueue(reqJson)
}
}
/* notifyReq["order_no"] = "订单号"
notifyReq["trade_no"] = "2022061322001402060501532448"
notifyReq["trade_status"] = "TRADE_SUCCESS"
notifyReq["notify_time"] = "2022-06-13 10:09:36"
notifyReq["total_amount"] = 202000
notifyReq["receipt_amount"] = 200000
var mapData = make(map[string]interface{})
mapData["data_type"] = "PayNotify"
mapData["param"] = map[string]interface{}{"payType": "aliPay", "notifyReq": notifyReq}
reqJson, _ := json.Marshal(mapData)
lib.PushMessToPayQueue(reqJson)*/
// 此写法是 gin 框架返回支付宝的写法
c.String(http.StatusOK, "%s", "success")
logger.Info("---------------------AlipayNotify END -------------")
return
}