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
|
package go_library
|
||||||
|
|
||||||
func Version() string {
|
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 (
|
import (
|
||||||
"bytes"
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package dssh
|
package ssh
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@ -1,4 +1,4 @@
|
|||||||
package dstring
|
package string
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
@ -1,4 +1,4 @@
|
|||||||
package dtime
|
package time
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package dtime
|
package time
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package durl
|
package url
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
@ -1,4 +1,4 @@
|
|||||||
package durl
|
package url
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@ -1,4 +1,4 @@
|
|||||||
package duuid
|
package uuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/google/uuid"
|
"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