增加微信支付V3版

master
李光春 3 years ago
parent 4fc777a884
commit 0b78d0f88f

@ -1,3 +1,7 @@
## v1.0.20 / 2021-08-16
## v1.0.21 / 2021-08-18
- 增加微信支付V3版
- ## v1.0.20 / 2021-08-16
- 增加电影票服务

@ -1,5 +1,5 @@
package go_library
func Version() string {
return "v1.0.20"
return "v1.0.21"
}

@ -5,6 +5,6 @@ import (
"testing"
)
func TestName(t *testing.T) {
func TestVersion(t *testing.T) {
fmt.Println(Version())
}

@ -0,0 +1,73 @@
package v3
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
type App struct {
AppId string // 小程序或者公众号的appid
AppSecret string
MchId string // 微信支付的商户id
AesKey string
ApiV3 string
PrivateSerialNo string // 私钥证书号
MchPrivateKey string // 路径 apiclient_key.pem
}
// ErrResp 错误返回
type ErrResp struct {
Code string `json:"code"`
Message string `json:"message"`
Detail struct {
Field string `json:"field,omitempty"`
Value interface{} `json:"value"`
Issue string `json:"issue,omitempty"`
Location string `json:"location"`
} `json:"detail"`
}
func (app *App) request(url string, params map[string]interface{}) (resp []byte, result ErrResp, err error) {
canonicalURL := fmt.Sprintf("%s/%s", WechatPayAPIServer, url)
method := "POST"
authorization, _ := app.authorization(method, params, canonicalURL)
marshal, _ := json.Marshal(params)
var req *http.Request
req, err = http.NewRequest(method, canonicalURL, bytes.NewReader(marshal))
if err != nil {
return
}
req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")
req.Header.Add("Accept-Language", "zh-CN")
req.Header.Add("Authorization", "WECHATPAY2-SHA256-RSA2048 "+authorization)
httpClient := &http.Client{}
var response *http.Response
response, err = httpClient.Do(req)
if err != nil {
return nil, result, err
}
// 处理成功
defer response.Body.Close()
resp, err = ioutil.ReadAll(response.Body)
// 检查错误
if err = json.Unmarshal(resp, &result); err != nil {
return nil, result, err
}
// 检查请求错误
if response.StatusCode == 200 {
return resp, result, err
}
return nil, result, err
}

@ -0,0 +1,7 @@
package v3
// 微信支付 API 地址
const (
WechatPayAPIServer = "https://api.mch.weixin.qq.com/v3" // 微信支付 API 地址
WechatPayAPIServerBackup = "https://api2.mch.weixin.qq.com/v3" // 微信支付 API 备份地址
)

@ -0,0 +1,95 @@
package v3
import (
"encoding/json"
"time"
)
type PayTransactionsJsapi struct {
Appid string `json:"appid"` //【是】应用ID
Mchid string `json:"mchid"` //【是】直连商户号
Description string `json:"description"` //【是】商品描述
OutTradeNo string `json:"out_trade_no"` //【是】商户订单号
TimeExpire time.Time `json:"time_expire,omitempty"` //【否】交易结束时间
Attach string `json:"attach,omitempty"` //【否】附加数据
NotifyUrl string `json:"notify_url"` //【是】通知地址
GoodsTag string `json:"goods_tag,omitempty"` //【否】订单优惠标记
Amount *PayTransactionsJsapiAmount `json:"amount"` //【是】订单金额
Payer *PayTransactionsJsapiPayer `json:"payer"` //【是】支付者
Detail *PayTransactionsJsapiDetail `json:"detail,omitempty"` //【否】优惠功能
SceneInfo *PayTransactionsJsapiSceneInfo `json:"scene_info,omitempty"` //【否】场景信息
SettleInfo *PayTransactionsJsapiSettleInfo `json:"settle_info,omitempty"` //【否】结算信息
}
// PayTransactionsJsapiAmount 订单金额
type PayTransactionsJsapiAmount struct {
Total int `json:"total"` //【是】总金额
Currency string `json:"currency,omitempty"` //【否】货币类型
}
// PayTransactionsJsapiPayer 支付者
type PayTransactionsJsapiPayer struct {
Openid string `json:"openid"` //【是】用户标识
}
// PayTransactionsJsapiDetail 优惠功能
type PayTransactionsJsapiDetail struct {
CostPrice int `json:"cost_price,omitempty"` //【否】订单原价
InvoiceId string `json:"invoice_id,omitempty"` //【否】商品小票ID
GoodsDetail []PayTransactionsJsapiDetailGoodsDetail `json:"goods_detail,omitempty"` //【否】单品列表
}
// PayTransactionsJsapiDetailGoodsDetail 单品列表
type PayTransactionsJsapiDetailGoodsDetail struct {
MerchantGoodsId string `json:"merchant_goods_id"` //【是】商户侧商品编码
WechatpayGoodsId string `json:"wechatpay_goods_id,omitempty"` //【否】微信侧商品编码
GoodsName string `json:"goods_name,omitempty"` //【否】商品名称
Quantity int `json:"quantity"` //【是】商品数量
UnitPrice int `json:"unit_price"` //【是】商品单价
}
// PayTransactionsJsapiSceneInfo 场景信息
type PayTransactionsJsapiSceneInfo struct {
PayerClientIp string `json:"payer_client_ip"` //【是】用户终端IP
DeviceId string `json:"device_id,omitempty"` //【否】商户端设备号
StoreInfo *PayTransactionsJsapiSceneInfoStoreInfo `json:"store_info,omitempty"` //【否】商户门店信息
}
// PayTransactionsJsapiSceneInfoStoreInfo 商户门店信息
type PayTransactionsJsapiSceneInfoStoreInfo struct {
Id string `json:"id"` //【是】门店编号
Name string `json:"name,omitempty"` //【否】门店名称
AreaCode string `json:"area_code,omitempty"` //【否】地区编码
Address string `json:"address,omitempty"` //【否】详细地址
}
// PayTransactionsJsapiSettleInfo 结算信息
type PayTransactionsJsapiSettleInfo struct {
ProfitSharing bool `json:"profit_sharing,omitempty"` //【否】是否指定分账
}
type PayTransactionsJsapiResult struct {
PrepayId string `json:"prepay_id"`
}
// PayTransactionsJsapi 小程序 JSAPI下单 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_5_1.shtml
func (app *App) PayTransactionsJsapi(param PayTransactionsJsapi) (resp PayTransactionsJsapiResult, result ErrResp, err error) {
// api params
params := map[string]interface{}{}
b, _ := json.Marshal(&param)
var m map[string]interface{}
_ = json.Unmarshal(b, &m)
for k, v := range m {
params[k] = v
}
body, result, err := app.request("pay/transactions/jsapi", params)
if err != nil {
return
}
if err = json.Unmarshal(body, &resp); err != nil {
return
}
return
}

@ -0,0 +1,121 @@
package v3
import (
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"gitee.com/dtapps/go-library/utils/random"
"io/ioutil"
"net/url"
"os"
"time"
)
// 对消息的散列值进行数字签名
func (app *App) signPKCS1v15(msg string, privateKey []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key decode error")
}
pri, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, errors.New("parse private key error")
}
key, ok := pri.(*rsa.PrivateKey)
if ok == false {
return nil, errors.New("private key format error")
}
sign, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, app.haSha256(msg))
if err != nil {
return nil, errors.New("sign error")
}
return sign, nil
}
// base编码
func (app *App) base64EncodeStr(src []byte) string {
return base64.StdEncoding.EncodeToString(src)
}
// sha256加密
func (app *App) haSha256(str string) []byte {
h := sha256.New()
h.Write([]byte(str))
return h.Sum(nil)
}
// 生成身份认证信息
func (app *App) authorization(method string, paramMap map[string]interface{}, rawUrl string) (token string, err error) {
var body string
if len(paramMap) != 0 {
paramJsonBytes, err := json.Marshal(paramMap)
if err != nil {
return token, err
}
body = string(paramJsonBytes)
}
urlPart, err := url.Parse(rawUrl)
if err != nil {
return token, err
}
canonicalUrl := urlPart.RequestURI()
timestamp := time.Now().Unix()
nonce := random.Alphanumeric(32)
message := fmt.Sprintf("%s\n%s\n%d\n%s\n%s\n", method, canonicalUrl, timestamp, nonce, body)
open, err := os.Open(app.MchPrivateKey) // 商户私有证书路径或者从数据库读取
if err != nil {
return token, err
}
defer open.Close()
privateKey, err := ioutil.ReadAll(open)
if err != nil {
return token, err
}
signBytes, err := app.signPKCS1v15(message, privateKey)
if err != nil {
return token, err
}
sign := app.base64EncodeStr(signBytes)
token = fmt.Sprintf("mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\"",
app.MchId, nonce, timestamp, app.PrivateSerialNo, sign)
return token, nil
}
// 报文解密
func (app *App) decryptGCM(aesKey, nonceV, ciphertextV, additionalDataV string) ([]byte, error) {
key := []byte(aesKey)
nonce := []byte(nonceV)
additionalData := []byte(additionalDataV)
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextV)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
aesGCM, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, additionalData)
if err != nil {
return nil, err
}
return plaintext, err
}

@ -1,4 +1,4 @@
package daes
package aes
import (
"bytes"

@ -1,11 +0,0 @@
package duuid_test
import (
"fmt"
"gitee.com/dtapps/go-library/utils/duuid"
"testing"
)
func TestName(t *testing.T) {
fmt.Println(duuid.GenUUID())
}

@ -0,0 +1,45 @@
package random
import (
"math/rand"
"time"
)
const numbers string = "0123456789"
const letters string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
const specials = "~!@#$%^*()_+-=[]{}|;:,./<>?"
const alphanumerics string = letters + numbers
const ascii string = alphanumerics + specials
func random(n int, chars string) string {
if n <= 0 {
return ""
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
bytes := make([]byte, n, n)
l := len(chars)
for i := 0; i < n; i++ {
bytes[i] = chars[r.Intn(l)]
}
return string(bytes)
}
// Alphanumeric 随机字母数字
func Alphanumeric(n int) string {
return random(n, alphanumerics)
}
// Alphabetic 随机字母
func Alphabetic(n int) string {
return random(n, letters)
}
// Numeric 随机数字
func Numeric(n int) string {
return random(n, numbers)
}
// Ascii 随机ASCII
func Ascii(n int) string {
return random(n, ascii)
}

@ -0,0 +1,22 @@
package random
import (
"fmt"
"testing"
)
func TestAlphanumeric(t *testing.T) {
fmt.Println(Alphanumeric(10))
}
func TestAlphabetic(t *testing.T) {
fmt.Println(Alphabetic(10))
}
func TestNumeric(t *testing.T) {
fmt.Println(Numeric(10))
}
func TestAscii(t *testing.T) {
fmt.Println(Ascii(10))
}

@ -1,4 +1,4 @@
package dssh
package ssh
import (
"fmt"

@ -1,4 +1,4 @@
package dssh
package ssh
import (
"testing"

@ -1,4 +1,4 @@
package dstring
package string
import (
"crypto/hmac"

@ -1,4 +1,4 @@
package dtime
package time
import "time"

@ -1,4 +1,4 @@
package dtime
package time
import (
"fmt"

@ -1,4 +1,4 @@
package durl
package url
import (
"io"

@ -1,4 +1,4 @@
package durl
package url
import (
"fmt"

@ -1,4 +1,4 @@
package duuid
package uuid
import (
"github.com/google/uuid"

@ -0,0 +1,11 @@
package uuid_test
import (
"fmt"
"gitee.com/dtapps/go-library/utils/uuid"
"testing"
)
func TestName(t *testing.T) {
fmt.Println(uuid.GenUUID())
}
Loading…
Cancel
Save