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"
|
||||
}
|
||||
|
@ -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(¶m)
|
||||
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…
Reference in new issue