2023-12-21 22:17:40 +08:00
|
|
|
|
package alipaycli
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2024-02-18 23:24:52 +08:00
|
|
|
|
"errors"
|
2023-12-21 22:17:40 +08:00
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/go-pay/gopay"
|
|
|
|
|
"github.com/go-pay/gopay/alipay"
|
2023-12-29 14:16:37 +08:00
|
|
|
|
"net/http"
|
2023-12-21 22:17:40 +08:00
|
|
|
|
"service/bizcommon/util"
|
|
|
|
|
"service/library/configcenter"
|
|
|
|
|
"service/library/logger"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
DefaultOrderTimeoutSeconds = 900 // 默认订单超时时间,单位: s
|
|
|
|
|
)
|
|
|
|
|
|
2024-02-18 23:24:52 +08:00
|
|
|
|
const (
|
|
|
|
|
AppIdXinYiDaoLe = "2021004115647165" // 心意到了
|
|
|
|
|
AppIdMiYuanTianShi = "2021004135664261" // 觅缘天使
|
|
|
|
|
)
|
2023-12-21 22:17:40 +08:00
|
|
|
|
|
2024-02-18 23:24:52 +08:00
|
|
|
|
var allAlipayClients = map[string]*AlipayClient{}
|
|
|
|
|
|
2024-02-18 23:47:58 +08:00
|
|
|
|
func GetDefaultAlipayClient() *AlipayClient {
|
2024-02-24 23:20:27 +08:00
|
|
|
|
return allAlipayClients[AppIdMiYuanTianShi]
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 23:47:58 +08:00
|
|
|
|
func GetAlipayClientByAppId(appId string) *AlipayClient {
|
|
|
|
|
return allAlipayClients[appId]
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-21 22:17:40 +08:00
|
|
|
|
type AlipayClient struct {
|
|
|
|
|
*alipay.Client
|
2024-02-18 23:24:52 +08:00
|
|
|
|
alipayPublicCertPath string
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 23:47:58 +08:00
|
|
|
|
func InitMulti(cfgList ...*configcenter.AlipayClientConfig) (err error) {
|
2024-02-18 23:24:52 +08:00
|
|
|
|
for _, cfg := range cfgList {
|
|
|
|
|
var cli *AlipayClient
|
|
|
|
|
cli, err = NewAlipayClient(cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if cli == nil {
|
|
|
|
|
err = errors.New("NewAlipayClient fail")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
allAlipayClients[cli.AppId] = cli
|
|
|
|
|
}
|
|
|
|
|
return
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 23:24:52 +08:00
|
|
|
|
func NewAlipayClient(cfg *configcenter.AlipayClientConfig) (ret *AlipayClient, err error) {
|
2023-12-21 22:17:40 +08:00
|
|
|
|
alipayCli, err := alipay.NewClient(cfg.Appid, cfg.PrivateKey, true)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("NewClient fail, cfg: %v, err: %v", util.ToJson(cfg), err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
alipayCli.SetNotifyUrl(cfg.NotifyUrl)
|
|
|
|
|
|
2024-01-08 14:25:27 +08:00
|
|
|
|
err = alipayCli.SetCertSnByPath(
|
2024-02-12 23:41:41 +08:00
|
|
|
|
cfg.AppCertPath,
|
|
|
|
|
cfg.AlipayRootCertPath,
|
|
|
|
|
cfg.AlipayPublicCertPath,
|
2024-01-08 14:25:27 +08:00
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("SetCertSnByPath fail, cfg: %v, err: %v", util.ToJson(cfg), err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-18 23:24:52 +08:00
|
|
|
|
ret = &AlipayClient{
|
|
|
|
|
Client: alipayCli,
|
|
|
|
|
alipayPublicCertPath: cfg.AlipayPublicCertPath,
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-29 14:16:37 +08:00
|
|
|
|
// 解析回调参数
|
|
|
|
|
func (c *AlipayClient) ParseNotify(req *http.Request) (notify gopay.BodyMap, err error) {
|
|
|
|
|
// 解析参数
|
|
|
|
|
notifyTmp, err := alipay.ParseNotifyToBodyMap(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("ParseNotifyToBodyMap fail, req: %v, err: %v", util.ToJson(req), err)
|
|
|
|
|
return
|
|
|
|
|
}
|
2023-12-29 20:47:34 +08:00
|
|
|
|
logger.Info("Alipay ParseNotify, %v", util.ToJson(notifyTmp))
|
2024-01-08 21:24:45 +08:00
|
|
|
|
|
2023-12-29 14:16:37 +08:00
|
|
|
|
// 验签
|
2024-02-18 23:24:52 +08:00
|
|
|
|
ok, err := alipay.VerifySignWithCert(c.alipayPublicCertPath, notifyTmp)
|
2023-12-29 14:16:37 +08:00
|
|
|
|
if !ok {
|
|
|
|
|
logger.Error("VerifySign fail, not ok, bm: %v", util.ToJson(notifyTmp))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
notify = notifyTmp
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-21 22:17:40 +08:00
|
|
|
|
// 支付宝 app支付
|
|
|
|
|
type AppPayParam struct {
|
|
|
|
|
OutTradeNo string // 商家订单id,我们自己的订单id
|
|
|
|
|
Subject string // 主题
|
|
|
|
|
TotalAmount int64 // 金额,单位:分
|
|
|
|
|
TimeOutSeconds int // 订单有效时间,单位:秒
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *AlipayClient) AppPay(ctx context.Context, param *AppPayParam) (alipayAppParamStr string, err error) {
|
|
|
|
|
if param.TimeOutSeconds <= 0 {
|
|
|
|
|
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
|
|
|
|
|
}
|
|
|
|
|
bm := gopay.BodyMap{
|
|
|
|
|
"out_trade_no": param.OutTradeNo,
|
|
|
|
|
"subject": param.Subject,
|
|
|
|
|
"total_amount": fmt.Sprintf("%.2f", float64(param.TotalAmount)/100.0),
|
|
|
|
|
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format("2006-01-02 15:04:05"),
|
2023-12-29 15:03:50 +08:00
|
|
|
|
"notify_url": c.NotifyUrl,
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
|
|
|
|
alipayAppParamStr, err = c.TradeAppPay(ctx, bm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logger.Info("alipay TradeAppPay param: %v", alipayAppParamStr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 支付宝 手机web支付
|
|
|
|
|
type WapPayParam struct {
|
|
|
|
|
OutTradeNo string // 商家订单id,我们自己的订单id
|
|
|
|
|
Subject string // 主题
|
|
|
|
|
TotalAmount int64 // 金额,单位:分
|
|
|
|
|
TimeOutSeconds int // 订单有效时间,单位:秒
|
2023-12-29 20:47:34 +08:00
|
|
|
|
ReturnUrl string // 支付宝return_url
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *AlipayClient) WapPay(ctx context.Context, param *WapPayParam) (alipayWapParamStr string, err error) {
|
|
|
|
|
if param.TimeOutSeconds <= 0 {
|
|
|
|
|
param.TimeOutSeconds = DefaultOrderTimeoutSeconds
|
|
|
|
|
}
|
|
|
|
|
bm := gopay.BodyMap{
|
|
|
|
|
"out_trade_no": param.OutTradeNo,
|
|
|
|
|
"subject": param.Subject,
|
|
|
|
|
"total_amount": fmt.Sprintf("%.2f", float64(param.TotalAmount)/100.0),
|
|
|
|
|
"time_expire": time.Now().Add(time.Second * time.Duration(param.TimeOutSeconds)).Format("2006-01-02 15:04:05"),
|
2023-12-29 15:03:50 +08:00
|
|
|
|
"notify_url": c.NotifyUrl,
|
2023-12-21 22:17:40 +08:00
|
|
|
|
}
|
2023-12-29 20:47:34 +08:00
|
|
|
|
if len(param.ReturnUrl) > 0 {
|
|
|
|
|
bm["return_url"] = param.ReturnUrl
|
|
|
|
|
}
|
2023-12-21 22:17:40 +08:00
|
|
|
|
alipayWapParamStr, err = c.TradeWapPay(ctx, bm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logger.Info("alipay TradeWapPay param: %v", alipayWapParamStr)
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-01-08 11:49:16 +08:00
|
|
|
|
|
|
|
|
|
// 支付宝单笔转账
|
|
|
|
|
type UniTransferParam struct {
|
|
|
|
|
OutBizNo string // 商家订单id,我们自己的订单id
|
|
|
|
|
Amount int64 // 金额,单位:分
|
|
|
|
|
Title string // 转账业务的标题,用于在支付宝用户的账单里显示。
|
|
|
|
|
AlipayLoginId string // 支付宝登录账号
|
|
|
|
|
AlipayName string // 支付宝真实姓名
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *AlipayClient) UniTransfer(ctx context.Context, param *UniTransferParam) (resp *alipay.FundTransUniTransferResponse, err error) {
|
|
|
|
|
bm := gopay.BodyMap{
|
|
|
|
|
"out_biz_no": param.OutBizNo,
|
|
|
|
|
"trans_amount": fmt.Sprintf("%.2f", float64(param.Amount)/100.0),
|
|
|
|
|
"biz_scene": "DIRECT_TRANSFER",
|
|
|
|
|
"product_code": "TRANS_ACCOUNT_NO_PWD",
|
|
|
|
|
"order_title": param.Title,
|
|
|
|
|
"payee_info": map[string]interface{}{
|
|
|
|
|
"identity": param.AlipayLoginId,
|
|
|
|
|
"identity_type": "ALIPAY_LOGON_ID",
|
|
|
|
|
"name": param.AlipayName,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
resp, err = c.FundTransUniTransfer(ctx, bm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logger.Info("alipay UniTransfer param: %v, resp: %v", bm.JsonBody(), util.ToJson(resp))
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-02-03 17:23:08 +08:00
|
|
|
|
|
|
|
|
|
// 订单查询
|
|
|
|
|
type QueryOrderParam struct {
|
|
|
|
|
OutTradeNo string // 商家订单id,我们自己的订单id
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *AlipayClient) QueryOrder(ctx context.Context, param *QueryOrderParam) (resp *alipay.TradeQueryResponse, err error) {
|
|
|
|
|
bm := gopay.BodyMap{
|
|
|
|
|
"out_trade_no": param.OutTradeNo,
|
|
|
|
|
}
|
|
|
|
|
resp, err = c.TradeQuery(ctx, bm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
2024-02-18 23:47:58 +08:00
|
|
|
|
|
|
|
|
|
// 退款申请
|
|
|
|
|
type RefundOneParam struct {
|
|
|
|
|
OutTradeNo string // 商家订单id,我们自己的订单id
|
|
|
|
|
RefundAmount int64 // 退款金额,单位:分
|
|
|
|
|
RefundReason string // 退款理由
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (c *AlipayClient) RefundOne(ctx context.Context, param *RefundOneParam) (resp *alipay.TradeRefundResponse, err error) {
|
|
|
|
|
if len(param.RefundReason) <= 0 {
|
|
|
|
|
param.RefundReason = "退款"
|
|
|
|
|
}
|
|
|
|
|
bm := gopay.BodyMap{
|
|
|
|
|
"out_trade_no": param.OutTradeNo,
|
|
|
|
|
"refund_amount": fmt.Sprintf("%.2f", float64(param.RefundAmount)/100.0),
|
|
|
|
|
"refund_reason": param.RefundReason,
|
|
|
|
|
}
|
|
|
|
|
resp, err = c.TradeRefund(ctx, bm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|