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的形式就可以了,暫時沒找到原因。