Go語言對接微信支付與退款全流程指南

tatasix發表於2024-10-08

目錄:
一、準備工作
二、初始化微信支付客戶端
三、實現支付功能
1. 付款時序圖
2. 實現不同場景下的支付
WAP端支付
PC端支付
Android端支付
3. 解析支付回撥
四、實現退款功能
退款時序圖
發起退款
解析退款回撥
五、總結

在網際網路技術日益發展的今天,線上支付已成為不可或缺的一部分。作為一門簡潔高效的程式語言,Go(又稱Golang)憑藉其強大的併發處理能力和高效效能,在後端開發領域越來越受到開發者的青睞。本文將詳細介紹如何使用Go語言對接微信支付,並實現支付和退款功能,幫助開發者快速上手。

一、準備工作

在開始編寫程式碼之前,你需要先準備好以下幾項工作:

  1. 註冊成為微信支付商戶:如果你還沒有微信支付商戶賬號,需要先前往微信支付商戶平臺完成註冊。
  2. 獲取必要的配置資訊
    • 商戶號 (MchId)
    • AppID (Appid)
    • API v3 金鑰 (ApiV3Key)
    • 商戶證書序列號 (MchSerialNo)
    • 私鑰 (PrivateKey)
    • 支付通知地址 (NotifyUrl)
    • 退款通知地址 (RefundUrl)
  3. 安裝第三方庫:為了簡化微信支付介面的呼叫,推薦使用github.com/go-pay/gopay這個庫。可以透過go get命令安裝:
    go get github.com/go-pay/gopay
    

二、初始化微信支付客戶端

首先,我們需要建立一個WechatPayService結構體來封裝微信支付的相關操作。該結構體包含上下文、配置資訊和微信支付客戶端例項。

type WechatPayService struct {
	ctx       context.Context
	config    WechatPayConfig
	wechatPay *wechat.ClientV3
}

type WechatPayConfig struct {
	Appid       string
	Appid1      string
	MchId       string
	ApiV3Key    string
	MchSerialNo string
	PrivateKey  string
	NotifyUrl   string
	RefundUrl   string
}

接著,我們透過NewWechatPayService函式來初始化WechatPayService例項。

func NewWechatPayService(ctx context.Context, config WechatPayConfig) *WechatPayService {
	client, err := wechat.NewClientV3(config.MchId, config.MchSerialNo, config.ApiV3Key, config.PrivateKey)
	if err != nil {
		fmt.Println(err)
		return nil
	}
	err = client.AutoVerifySign()
	if err != nil {
		fmt.Println(err)
		return nil
	}
	client.DebugSwitch = gopay.DebugOn

	return &WechatPayService{
		ctx:       ctx,
		wechatPay: client,
		config:    config,
	}
}

此程式碼段中,我們透過NewClientV3方法初始化了微信支付客戶端,傳入商戶號、證書序列號、API v3金鑰和私鑰等關鍵引數。為了保障支付的安全性,開啟了自動驗籤功能。

三、實現支付功能

1. 付款時序圖

2. 實現不同場景下的支付

WAP端支付

func (w *WechatPayService) WapPay(charge *Charge) (result string, err error) {
	amount := decimal.NewFromInt(charge.MoneyFee).DivRound(decimal.NewFromInt(1), 2).IntPart()
	expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
	bm := make(gopay.BodyMap)
	bm.Set("appid", w.config.Appid).
		Set("mchid", w.config.MchId).
		Set("description", charge.Describe).
		Set("out_trade_no", charge.TradeNum).
		Set("time_expire", expire).
		Set("notify_url", w.config.NotifyUrl).
		SetBodyMap("amount", func(bm gopay.BodyMap) {
			bm.Set("total", amount).
				Set("currency", "CNY")
		}).
		SetBodyMap("scene_info", func(bm gopay.BodyMap) {
			bm.Set("payer_client_ip", "127.0.0.1").
				SetBodyMap("h5_info", func(bm gopay.BodyMap) {
					bm.Set("type", "Wap")
				})
		})

	rsp, err := w.wechatPay.V3TransactionH5(w.ctx, bm)
	if err != nil {
		return
	}
	result = rsp.Response.H5Url
	return
}

PC端支付

func (w *WechatPayService) PcPay(charge *Charge) (result string, err error) {
	amount := decimal.NewFromInt(charge.MoneyFee).DivRound(decimal.NewFromInt(1), 2).IntPart()
	expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
	bm := make(gopay.BodyMap)
	bm.Set("appid", w.config.Appid).
		Set("mchid", w.config.MchId).
		Set("description", charge.Describe).
		Set("out_trade_no", charge.TradeNum).
		Set("time_expire", expire).
		Set("notify_url", w.config.NotifyUrl).
		SetBodyMap("amount", func(bm gopay.BodyMap) {
			bm.Set("total", amount).
				Set("currency", "CNY")
		})

	rsp, err := w.wechatPay.V3TransactionNative(w.ctx, bm)
	if err != nil {
		return
	}
	result = rsp.Response.CodeUrl
	return
}

Android端支付

func (w *WechatPayService) AndroidPay(charge *Charge) (result string, err error) {
	amount := decimal.NewFromInt(charge.MoneyFee).DivRound(decimal.NewFromInt(1), 2).IntPart()
	expire := time.Now().Add(10 * time.Minute).Format(time.RFC3339)
	bm := make(gopay.BodyMap)
	bm.Set("appid", w.config.Appid1).
		Set("mchid", w.config.MchId).
		Set("description", charge.Describe).
		Set("out_trade_no", charge.TradeNum).
		Set("time_expire", expire).
		Set("notify_url", w.config.NotifyUrl).
		SetBodyMap("amount", func(bm gopay.BodyMap) {
			bm.Set("total", amount).
				Set("currency", "CNY")
		})

	rsp, err := w.wechatPay.V3TransactionApp(w.ctx, bm) 
	if err != nil {
		return
	}

	jsapiInfo, err := w.wechatPay.PaySignOfApp(w.config.Appid1, rsp.Response.PrepayId)
	str, _ := json.Marshal(jsapiInfo)
	result = string(str)
	return
}
  • APP支付跟JSAPI支付很像。主要區別在於app與商戶服務後臺的互動。app會從商戶服務後臺獲取簽名資訊,根據簽名資訊,app直接呼叫微信支付服務下單。

3. 解析支付回撥

當使用者完成支付後,微信會向我們的伺服器傳送支付成功的回撥通知。我們需要解析這個通知並驗證其合法性。

func (w *WechatPayService) GetNotifyResult(r *http.Request) (res *wechat.V3DecryptResult, err error) {
	notifyReq, err := wechat.V3ParseNotify(r)    // 解析回撥引數
	if err != nil {
		fmt.Println(err)
		return
	}
	if notifyReq == nil {
		return
	}
	return notifyReq.DecryptCipherText(w.config.ApiV3Key)  // 解密回撥內容
}

透過V3ParseNotify方法,解析支付通知,並使用API v3金鑰解密支付結果。

四、實現退款功能

退款時序圖

發起退款

除了支付,退款也是微信支付中常用的功能。接下來,我們來看如何使用Go語言實現退款功能。

當需要對已支付的訂單進行退款時,可以呼叫Refund方法。

func (w *WechatPayService) Refund(charge *RefundCharge) (err error) {
	amount := decimal.NewFromInt(charge.MoneyFee).DivRound(decimal.NewFromInt(1), 2).IntPart()
	bm := make(gopay.BodyMap)
	bm.Set("out_trade_no", charge.TradeNum).
		Set("out_refund_no", charge.OutRefundNo).
		Set("reason", charge.RefundReason).
		Set("notify_url", w.config.RefundUrl).
		SetBodyMap("amount", func(bm gopay.BodyMap) {
			bm.Set("total", amount).
				Set("refund", amount).
				Set("currency", "CNY")
		})

	rsp, err := w.wechatPay.V3Refund(w.ctx, bm)  // 發起退款請求
	if err != nil {
		return
	}

	if rsp == nil || rsp.Response == nil || rsp.Error != "" {
        // 處理退款錯誤
		err = errors.New(rsp.Error) 
		return
	}
	return
}

解析退款回撥

func (w *WechatPayService) GetRefundNotifyResult(r *http.Request) (res *wechat.V3DecryptRefundResult, err error) {
	notifyReq, err := wechat.V3ParseNotify(r)
	if err != nil {
		return
	}
	return notifyReq.DecryptRefundCipherText(w.config.ApiV3Key)
}

五、總結

透過本文的介紹,相信你已經掌握瞭如何使用Go語言對接微信支付,並實現了支付和退款功能。這些功能不僅能夠提升使用者體驗,還能幫助你在實際專案中更加高效地處理支付相關的業務邏輯。希望本文對你有所幫助!

如果你有任何問題或建議,歡迎在評論區留言交流。期待你的寶貴意見!

相關文章