1. 程式人生 > >golang支付寶支付生成簽名

golang支付寶支付生成簽名

呼叫支付寶支付介面時,需要用商戶自己的私鑰生成sign,將資料與sign一起傳送給支付寶來發起支付。

這裡總結一下簽名的流程,以支付寶手機網站支付為例。實現語言為golang。

請求引數網址:https://docs.open.alipay.com/203/107090/

一. 生成biz_content業務引數資訊:

func GenBizContent(subject, outTradeNo, buyerId, payType string, totalAmount int64) (string, error) {
	m := make(map[string]interface{})

	m["subject"] = subject
	m["out_trade_no"] = outTradeNo
	PayMoney, err := Int64DividedBy100(totalAmount)
	if err != nil {
		err = errors.New("change amount int64 to float64 fail," + err.Error())
		return "", err
	}
	m["total_amount"] = PayMoney //TODO
	switch payType {
	case constants.PayTypeAlipayWap:
		m["product_code"] = AlipayWapProductCode
	case constants.PayTypeAlipayApp:
		m["product_code"] = AlipayAppProductCode
	case constants.PayTypeAlipayMini:
		m["buyer_id"] = buyerId
	}

	jsonStr, err := json.Marshal(m)
	if err != nil {
		err = errors.New("generate biz_content fail," + err.Error())
		return "", err
	}
	return string(jsonStr), nil
}

func Int64DividedBy100(amount int64) (float64, error) {
	str := strconv.FormatInt(amount, 10)
	switch len(str) {
	case 1:
		str = "0.0" + str
	case 2:
		str = "0." + str
	default:
		str = str[:len(str)-2] + "." + str[len(str)-2:]
	}
	ret, err := strconv.ParseFloat(str, 64)
	if err != nil {
		err = errors.Wrap(err)
		return -0.1, err
	}
	return ret, nil
}

這裡定義map[string]interface{}來儲存biz_content中的必填內容,total_amount欄位傳進來時單位為‘分’,需要轉換成‘元’,為了避免浮點數運算產生誤差,這裡轉換成字串模擬除法,最後轉換回來,也可以用特定的Money包(github.com/chanxuehong/util/money)。最後利用json.Marshel將map形式轉換成string。另外嘗試過這裡total_amount為float或者string都可以。

二. 生成sign

1. 這裡先將簽名需要的資料填充到url.Values{}中,


func FillSign2Data(appid, payType, outTradeNo, bizContent, privateKey string, userId int64, method string) (url.Values, error) {
	data := url.Values{}
	data.Set("app_id", appid)
	data.Set("method", method)
	data.Set("charset", "utf-8")
	data.Set("sign_type", "RSA2")
	now := time.Now().Format(TimestampForm)
	data.Set("timestamp", now)
	data.Set("version", "1.0")
	data.Set("notify_url", fmt.Sprintf(config.AlipayCallBack, userId, outTradeNo))
	data.Set("biz_content", bizContent)
	dlog.Debug("signed_data", data)
	//生成簽名
	signContentBytes, _ := url.QueryUnescape(data.Encode())
	dlog.Debug("data to be signed", signContentBytes)
	//fmt.Println("data to be signed", signContentBytes)
	signature, err := util.Sign([]byte(signContentBytes), "RSA2", privateKey)
	if err != nil {
		err = errors.Errorf("生成簽名失敗,請檢查私鑰是否配置成功。error:%v", err)
		return nil, err
	}
	data.Set("sign", signature)
	dlog.Debug("sign", signature)
	return data, nil
}

注意這裡url.Encode()函式會將每個引數進行url編碼,然後引數之間用&符號連線,對應的key和value用'='連線。

然而支付寶的資料只要求引數之間用&符號連線,對應的key和value用'='連線,對每個引數是不用url編碼的,所以這裡將編碼後的url再解碼,即將url編碼的引數解碼回原先的字元。

這裡有例子:

func testUrl() {

	data := url.Values{}
	data.Set("1", "1")
	data.Set("2", "2")
	data.Set("liyunlong", "liyunlong")
	data.Set("data", "http://alipared:10003/paydcaldlback/alidpay/432412/Nofaffa")


	fmt.Println(data.Encode())
	//tmp := url.Values{"data":[]{"data is nklsjfklajf"}}
	fmt.Println(url.QueryUnescape(data.Encode()))


	os.Exit(0)
}

輸出:
1=1&2=2&data=http%3A%2F%2Falipared%3A10003%2Fpaydcaldlback%2Falidpay%2F432412%2FNofaffa&liyunlong=liyunlong
1=1&2=2&data=http://alipared:10003/paydcaldlback/alidpay/432412/Nofaffa&liyunlong=liyunlong <nil>

2. 生成sign

func Sign(data []byte, SignType, pemPriKey string) (signature string, err error) {
	var h hash.Hash
	var hType crypto.Hash
	switch SignType {
	case SignTypeRsa:
		h = sha1.New()
		hType = crypto.SHA1
	case SignTypeRsa2:
		h = sha256.New()
		hType = crypto.SHA256
	}
	h.Write(data)
	d := h.Sum(nil)
	pk, err := ParsePrivateKey(pemPriKey)
	if err != nil {
		err = errors.Wrap(err)
		return
	}
	bs, err := rsa.SignPKCS1v15(rand.Reader, pk, hType, d)

	if err != nil {
		err = errors.Wrap(err)
		return
	}
	signature = base64.StdEncoding.EncodeToString(bs)
	return
}

func ParsePrivateKey(privateKey string) (pk *rsa.PrivateKey, err error) {
	block, _ := pem.Decode([]byte(privateKey))
	if block == nil {
		err = errors.Errorf("私鑰格式錯誤1:%s", privateKey)
		return
	}
	switch block.Type {
	case "RSA PRIVATE KEY":
		rsaPrivateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
		if err == nil {
			pk = rsaPrivateKey
		} else {
			err = errors.Wrap(err)
		}
	default:
		err = errors.Errorf("私鑰格式錯誤:%s", privateKey)
	}
	return
}

這裡利用rsa2方式,先將要簽名的資料用sha256的形式來hash,可以利用sha256.sum256直接來hash資料,上面寫的稍微麻煩點,實際是一樣的。然後呼叫SignPKCS1v15來生成sign,傳入轉換後的私鑰、加密方式、hash之後的資料,即可獲取sign。最後將sign進行base64編碼即可。

三.期間遇到的問題總結:

除錯過程中經常碰到傳給支付寶相關資料後,支付寶驗籤失敗的情況(手機網站支付方式),主要原因有以下幾種:

1. 私鑰公鑰不匹配。這是很常見的,一定要確認好,可以利用支付寶沙箱模式和支付寶簽名工具檢測。

2. 少傳了引數。在服務端生成簽名時,利用了某些引數,但是傳給支付寶時,由於不是必填引數,就沒填,這樣也會導致驗籤失敗。

3. biz_content中存在中文,出現亂碼。wap提交的是form表單,我改成get的形式就可以了,暫時沒找到原因。