1. go语言推荐使用第三方支付库 github.com/go-pay/gopay
  2. 说明
  1. 支付宝和应用都有公私钥,即2套,为什么是2套?
  1. 用户通过应用私钥加密发送消息给支付宝,支付宝通过应用公钥解密;
  2. 支付宝通过支付宝私钥加密发送消息给用户,用户通过支付宝公钥解密。
  1. 加签和验签
  1. sign生成规则:
  1. 发送给支付宝的所有参数、剔除sign、sign_type
  2. 按照参数字母顺序排序,且格式为key=value,参数之间使用&连接
  3. 使用对应的sign_type转换,如MD5
  1. 用户发送给支付宝的消息需要加签,即按照规则生成sign然后以参数sign传递过去,因为支付宝需要校验请求的正确性
  2. 支付宝回调的通知请求需要验签,因为用户也需要验证支付宝请求的正确性
  1. 工具
  1. 沙箱工具及使用 https://opendocs.alipay.com/common/02kkv7
  2. 支付应用文档可参考请求和响应参数 https://opendocs.alipay.com/open/02e7gq?scene=20
  3. 支付宝开放平台
  4. 异步通知URL,需要能被外网访问的网络,可以使用免费内网穿透工具生成域名ngrok 官网甚至有使用介绍,方便快捷
  1. 代码(手机网页支付,手机上提前下载好沙箱版支付宝)
package main

import (
	"context"
	"errors"
	"fmt"
	"github.com/go-pay/gopay"
	"github.com/go-pay/gopay/alipay"
	"github.com/go-pay/gopay/pkg/xlog"
	"github.com/kataras/iris/v12"
	"net/url"
)

//应用私钥
var privateKey = ``

func main() {
	// 1初始化支付宝客户端
	client, err := alipay.NewClient("appid", privateKey, false)
	if err != nil {
		xlog.Error(err)
		return
	}
	// 打开Debug开关,输出日志,默认关闭
	//client.DebugSwitch = gopay.DebugOn
	// 设置支付宝请求 公共参数
	client.SetLocation(alipay.LocationShanghai). // 设置时区,不设置或出错均为默认服务器时间
							SetCharset(alipay.UTF8).                                    // 设置字符编码,不设置默认 utf-8
							SetSignType(alipay.RSA2).                                   // 设置签名类型,不设置默认 RSA2
							SetReturnUrl("https://127.0.0.1:80/return").          //跳转页面
							SetNotifyUrl("https://本地地址80端口映射的域名/notify") // 异步通知URL 可被外网访问 post请求//.SetAppAuthToken() // 设置第三方应用授权

	// 自动同步验签(只支持证书模式)
	// 传入 alipayCertPublicKey_RSA2.crt 内容
	//	client.AutoVerifySign([]byte(`-----BEGIN CERTIFICATE-----
	//-----END CERTIFICATE-----
	//`))

	// 公钥证书模式,需要传入证书,证书路径
	err = client.SetCertSnByPath("appCertPublicKey.crt", "alipayRootCert.crt", "alipayCertPublicKey_RSA2.crt")
	if err != nil {
		xlog.Error(err)
		return
	}

	// 2web服务器
	app := iris.New()
	app.Get("/pay", func(ctx iris.Context) { //支付
		Pay(context.Background(), client)
	})
	app.Get("/return", func(ctx iris.Context) { //返回页
		ReturnUrl(ctx)
	})
	app.Post("/notify", func(ctx iris.Context) { //通知页
		err = NotifyUrl(ctx)
		if err != nil {
			fmt.Println("通知页", err)
		}
	})
	err = app.Run(iris.Addr(":80"))
	if err != nil {
		fmt.Println(err)
		return
	}
}

// Pay 手机网站支付
func Pay(ctx context.Context, client *alipay.Client) {
	//请求参数
	bm := make(gopay.BodyMap)
	bm.Set("subject", "手机网站支付")
	bm.Set("out_trade_no", "LP20220628") //一个订单号只能支付一次
	bm.Set("total_amount", "0.01")       //1分钱
	fmt.Println("body:", bm)
	//发送请求
	payUrl, err := client.TradeWapPay(ctx, bm) //内部已经处理了签名
	if err != nil {
		xlog.Error(err)
		return
	}
	//支付界面链接,直接粘贴到手机浏览器里即可支付
	fmt.Println(payUrl)
}

// ReturnUrl 返回页 同步get,可在本机上测试
func ReturnUrl(ctx iris.Context) {
	ctx.WriteString("ReturnUrl success")
}

// NotifyUrl 通知页 异步post,必须外网可访问,必须返回success否则会一直通知
func NotifyUrl(ctx iris.Context) error {
	body, err := ctx.GetBody()
	if err != nil {
		fmt.Errorf("%v", err)
		return err
	}
	fmt.Println("body", string(body))
	values, err := url.ParseQuery(string(body))
	if err != nil {
		fmt.Errorf("%v", err)
		return err
	}
	datas, err := alipay.ParseNotifyByURLValues(values) //证书异步验签
	if err != nil {
		fmt.Errorf("%v", err)
		return err
	}
	//验签
	ok, err := alipay.VerifySignWithCert("alipayCertPublicKey_RSA2.crt", datas)
	if ok == false || err != nil {
		fmt.Errorf("%v", err)
		return errors.New("校验失败")
	}
	fmt.Println(datas)
	tradeStatus := datas.Get("trade_status")
	fmt.Printf(tradeStatus)
	//todo 处理业务逻辑
	//交易状态
	if tradeStatus == "TRADE_SUCCESS" {
		fmt.Println("交易成功")
	} else {
		fmt.Errorf("交易异常")
	}
	//返回success
	ctx.WriteString("success")
	return nil
}