| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- package wx_mp
- import (
- "bytes"
- "crypto"
- "crypto/rand"
- "crypto/rsa"
- "crypto/sha256"
- "crypto/x509"
- "encoding/base64"
- "encoding/json"
- "encoding/pem"
- "fmt"
- "io/ioutil"
- "net/http"
- "net/url"
- "strings"
- "time"
- "github.com/astaxie/beego"
- "github.com/uuid"
- )
- const (
- MerchantTransferAccepted = "ACCEPTED"
- MerchantTransferProcessing = "PROCESSING"
- MerchantTransferWaitUserConfirm = "WAIT_USER_CONFIRM"
- MerchantTransferTransfering = "TRANSFERING"
- MerchantTransferSuccess = "SUCCESS"
- MerchantTransferFail = "FAIL"
- MerchantTransferCancelled = "CANCELLED"
- MerchantTransferCanceling = "CANCELING"
- )
- type MerchantTransferReportInfo struct {
- InfoType string `json:"info_type"`
- InfoContent string `json:"info_content"`
- }
- type MerchantTransferRequest struct {
- AppId string `json:"appid"`
- OutBillNo string `json:"out_bill_no"`
- TransferSceneId string `json:"transfer_scene_id"`
- OpenId string `json:"openid"`
- TransferAmount int64 `json:"transfer_amount"`
- TransferRemark string `json:"transfer_remark"`
- NotifyUrl string `json:"notify_url,omitempty"`
- UserRecvPerception string `json:"user_recv_perception,omitempty"`
- TransferSceneReportInfos []MerchantTransferReportInfo `json:"transfer_scene_report_infos,omitempty"`
- }
- type MerchantTransferResponse struct {
- OutBillNo string `json:"out_bill_no"`
- TransferBillNo string `json:"transfer_bill_no"`
- CreateTime string `json:"create_time"`
- State string `json:"state"`
- FailReason string `json:"fail_reason"`
- PackageInfo string `json:"package_info"`
- Code string `json:"code"`
- Message string `json:"message"`
- RawBody string `json:"-"`
- HttpStatus int `json:"-"`
- }
- func MerchantTransfer(openid string, amount int64, outBillNo, payCode, remark string) (*MerchantTransferResponse, error) {
- appId := beego.AppConfig.String("WxFohowXcxAppId")
- mechantInfo := GetMechantInfo(payCode)
- if remark == "" {
- remark = merchantTransferConfig("MchTransferRemark", "佣金提现")
- }
- req := MerchantTransferRequest{
- AppId: appId,
- OutBillNo: outBillNo,
- TransferSceneId: merchantTransferConfig("MchTransferSceneId", "1005"),
- OpenId: openid,
- TransferAmount: amount,
- TransferRemark: remark,
- NotifyUrl: merchantTransferConfig("MchTransferNotifyUrl", ""),
- UserRecvPerception: merchantTransferConfig("MchTransferUserRecvPerception", "劳务报酬"),
- TransferSceneReportInfos: merchantTransferReportInfos(),
- }
- body, err := json.Marshal(req)
- if err != nil {
- return nil, err
- }
- return doMerchantTransferRequest("POST", "/v3/fund-app/mch-transfer/transfer-bills", body, mechantInfo)
- }
- func QueryMerchantTransferByOutBillNo(outBillNo, payCode string) (*MerchantTransferResponse, error) {
- mechantInfo := GetMechantInfo(payCode)
- path := "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/" + url.PathEscape(outBillNo)
- return doMerchantTransferRequest("GET", path, nil, mechantInfo)
- }
- func CancelMerchantTransferByOutBillNo(outBillNo, payCode string) (*MerchantTransferResponse, error) {
- mechantInfo := GetMechantInfo(payCode)
- path := "/v3/fund-app/mch-transfer/transfer-bills/out-bill-no/" + url.PathEscape(outBillNo) + "/cancel"
- return doMerchantTransferRequest("POST", path, nil, mechantInfo)
- }
- func MerchantTransferStatePending(state string) bool {
- switch state {
- case MerchantTransferAccepted, MerchantTransferProcessing, MerchantTransferWaitUserConfirm, MerchantTransferTransfering, MerchantTransferCanceling:
- return true
- default:
- return false
- }
- }
- func merchantTransferConfig(name, def string) string {
- value := strings.TrimSpace(beego.AppConfig.String(name))
- if value == "" {
- return def
- }
- return value
- }
- func merchantTransferReportInfos() []MerchantTransferReportInfo {
- raw := merchantTransferConfig("MchTransferReportInfos", "岗位类型:推广员,报酬说明:佣金提现")
- if raw == "" {
- return nil
- }
- var list []MerchantTransferReportInfo
- for _, item := range strings.Split(raw, ",") {
- item = strings.TrimSpace(item)
- if item == "" {
- continue
- }
- parts := strings.SplitN(item, ":", 2)
- if len(parts) != 2 {
- continue
- }
- infoType := strings.TrimSpace(parts[0])
- infoContent := strings.TrimSpace(parts[1])
- if infoType != "" && infoContent != "" {
- list = append(list, MerchantTransferReportInfo{InfoType: infoType, InfoContent: infoContent})
- }
- }
- return list
- }
- func doMerchantTransferRequest(method, path string, body []byte, mechantInfo MechantPayInfo) (*MerchantTransferResponse, error) {
- timestamp := fmt.Sprintf("%d", time.Now().Unix())
- nonce := strings.Replace(uuid.NewV4().String(), "-", "", -1)
- bodyText := ""
- if len(body) > 0 {
- bodyText = string(body)
- }
- signature, serialNo, err := signWechatPayV3Request(method, path, timestamp, nonce, bodyText, mechantInfo)
- if err != nil {
- return nil, err
- }
- req, err := http.NewRequest(method, "https://api.mch.weixin.qq.com"+path, bytes.NewReader(body))
- if err != nil {
- return nil, err
- }
- req.Header.Set("Accept", "application/json")
- req.Header.Set("Content-Type", "application/json")
- req.Header.Set("Authorization", fmt.Sprintf(`WECHATPAY2-SHA256-RSA2048 mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"`, mechantInfo.MchId, nonce, signature, timestamp, serialNo))
- resp, err := http.DefaultClient.Do(req)
- if err != nil {
- return nil, err
- }
- defer resp.Body.Close()
- respBody, _ := ioutil.ReadAll(resp.Body)
- ret := &MerchantTransferResponse{RawBody: string(respBody), HttpStatus: resp.StatusCode}
- if len(respBody) > 0 {
- _ = json.Unmarshal(respBody, ret)
- }
- if resp.StatusCode < 200 || resp.StatusCode >= 300 {
- if ret.Message == "" {
- ret.Message = string(respBody)
- }
- return ret, fmt.Errorf("wechat merchant transfer status=%d code=%s message=%s", resp.StatusCode, ret.Code, ret.Message)
- }
- return ret, nil
- }
- func signWechatPayV3Request(method, path, timestamp, nonce, body string, mechantInfo MechantPayInfo) (string, string, error) {
- privateKey, err := loadMerchantPrivateKey(mechantInfo.MchKeyFile)
- if err != nil {
- return "", "", err
- }
- serialNo, err := loadMerchantCertSerialNo(mechantInfo.MchCertFile)
- if err != nil {
- return "", "", err
- }
- message := strings.ToUpper(method) + "\n" + path + "\n" + timestamp + "\n" + nonce + "\n" + body + "\n"
- h := sha256.Sum256([]byte(message))
- signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, h[:])
- if err != nil {
- return "", "", err
- }
- return base64.StdEncoding.EncodeToString(signature), serialNo, nil
- }
- func loadMerchantPrivateKey(filename string) (*rsa.PrivateKey, error) {
- keyData, err := ioutil.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- block, _ := pem.Decode(keyData)
- if block == nil {
- return nil, fmt.Errorf("invalid merchant private key pem")
- }
- switch block.Type {
- case "PRIVATE KEY":
- key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
- if err != nil {
- return nil, err
- }
- privateKey, ok := key.(*rsa.PrivateKey)
- if !ok {
- return nil, fmt.Errorf("merchant private key is not rsa")
- }
- return privateKey, nil
- case "RSA PRIVATE KEY":
- return x509.ParsePKCS1PrivateKey(block.Bytes)
- default:
- return nil, fmt.Errorf("unsupported private key type: %s", block.Type)
- }
- }
- func loadMerchantCertSerialNo(filename string) (string, error) {
- certData, err := ioutil.ReadFile(filename)
- if err != nil {
- return "", err
- }
- block, _ := pem.Decode(certData)
- if block == nil {
- return "", fmt.Errorf("invalid merchant cert pem")
- }
- cert, err := x509.ParseCertificate(block.Bytes)
- if err != nil {
- return "", err
- }
- return strings.ToUpper(cert.SerialNumber.Text(16)), nil
- }
|