├── .gitignore ├── resource ├── tree.png ├── cloud.jpg ├── qrcode.png ├── cha-cha-ender.mp3 ├── 3d_ocean_1590675653.mp4 └── README.md ├── .editorconfig ├── config ├── model_test.yaml ├── constant.go ├── model_test.go └── model.go ├── controllers ├── payment │ ├── redpack │ │ ├── work.go │ │ └── redpack.go │ ├── apply4Sub │ │ └── apply4Sub.go │ ├── merchantService │ │ └── merchantService.go │ ├── bill.go │ ├── tax │ │ └── tax.go │ ├── paymentScore │ │ └── paymentScore.go │ ├── merchant │ │ └── merchant.go │ ├── security.go │ ├── transfer │ │ ├── batch.go │ │ └── transfer.go │ ├── profitSharing.go │ ├── refund │ │ └── refund.go │ └── partner │ │ └── payment.go ├── wecom │ ├── jssdk.go │ ├── base.go │ ├── miniprogram.go │ ├── oa │ │ ├── dial.go │ │ ├── pstncc.go │ │ ├── approval.go │ │ ├── journal.go │ │ ├── calendar.go │ │ ├── meeting.go │ │ ├── webdrive.go │ │ ├── schedule.go │ │ ├── living.go │ │ ├── meetingroom.go │ │ └── checkin.go │ ├── user │ │ ├── validate │ │ │ └── user-create.go │ │ ├── message.go │ │ ├── linked-corp.go │ │ ├── user-callback.go │ │ ├── tag.go │ │ └── department.go │ ├── account-service │ │ ├── service-state.go │ │ ├── servicer.go │ │ ├── message.go │ │ ├── account-service.go │ │ └── customer.go │ ├── corpgroup.go │ ├── external-contact │ │ ├── statistic.go │ │ ├── group-chat.go │ │ └── transfer.go │ ├── msg-audit.go │ ├── message │ │ └── message-callback.go │ ├── invoice.go │ ├── media.go │ └── oauth-controller.go ├── official-account │ ├── reply.go │ ├── card.go │ ├── short-url.go │ ├── qrcode.go │ ├── base.go │ ├── oauth.go │ ├── jssdk.go │ ├── uniform-message.go │ ├── user-tag.go │ ├── user.go │ ├── server.go │ ├── comment.go │ └── menu.go ├── miniprogram │ ├── internet.go │ ├── short-link.go │ ├── virtual-pay.go │ ├── url-link.go │ ├── phone-number.go │ ├── soter.go │ ├── risk-control.go │ ├── url-scheme.go │ ├── service-market.go │ ├── updatable-message.go │ ├── server.go │ ├── search.go │ ├── wxacode.go │ ├── uniform-message.go │ ├── plugin-manager.go │ ├── security.go │ ├── near-by-poi.go │ ├── auth.go │ ├── img.go │ ├── industry.go │ └── subscribe-message.go └── open-platform │ ├── server.go │ └── open-platform.go ├── docs ├── md │ ├── official-account.getCallbackIp.md │ └── official-account.clearQuota.md ├── swagger.yaml ├── swagger.json └── docs.go ├── templates ├── openplatform-auth.html └── h5-pay.html ├── services ├── get-env.go ├── open-platform.go ├── offiaccount-service.go ├── miniprogram-service.go ├── payment-service.go └── wecom-service.go ├── routes ├── router.go ├── open-platform.go └── payment.go ├── main.go ├── README.md ├── go.mod └── config-example.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | config.yml 4 | certs/ 5 | 6 | *.log -------------------------------------------------------------------------------- /resource/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtisanCloud/PowerWechatTutorial/HEAD/resource/tree.png -------------------------------------------------------------------------------- /resource/cloud.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtisanCloud/PowerWechatTutorial/HEAD/resource/cloud.jpg -------------------------------------------------------------------------------- /resource/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtisanCloud/PowerWechatTutorial/HEAD/resource/qrcode.png -------------------------------------------------------------------------------- /resource/cha-cha-ender.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtisanCloud/PowerWechatTutorial/HEAD/resource/cha-cha-ender.mp3 -------------------------------------------------------------------------------- /resource/3d_ocean_1590675653.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ArtisanCloud/PowerWechatTutorial/HEAD/resource/3d_ocean_1590675653.mp4 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | 7 | [*.go] 8 | indent_style = tab 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /resource/README.md: -------------------------------------------------------------------------------- 1 | 2 | cha-cha-ender.mp3 [来源地址](https://freepd.com/comedy.php) 3 | 4 | 3d_ocean_1590675653.mp4 [来源地址](https://www.videvo.net/video/flying-over-bright-blue-open-ocean/514903/) -------------------------------------------------------------------------------- /config/model_test.yaml: -------------------------------------------------------------------------------- 1 | appid: hello-app 2 | key: test-key 3 | certpath: certs/apiclient_cert.pem 4 | keypath: certs/apiclient_key.pem 5 | serialno: 55D06F99FF64CF1759F1E5B77A0BEC8B67A78C2E 6 | mchapiv3key: fjaiofadfafafoafjia 7 | notifyurl: https://powerwechat.artisan-cloud.com 8 | -------------------------------------------------------------------------------- /config/constant.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const API_RETURN_CODE_INIT = 200 4 | const API_RETURN_CODE_WARNING = 300 5 | const API_RETURN_CODE_ERROR = 400 6 | const API_RETURN_CODE_NOT_FOUND = 404 7 | 8 | //--------------------------------------------- 9 | 10 | const API_RESULT_CODE_INIT = 0 11 | 12 | 13 | const API_ERR_CODE_REQUEST_PARAM_ERROR = 401032 -------------------------------------------------------------------------------- /controllers/payment/redpack/work.go: -------------------------------------------------------------------------------- 1 | package redpack 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func APIWorkSendWXRedpack(c *gin.Context) { 8 | 9 | //payConf, err := services.PaymentApp.RedPack.SendWorkWX(response.PrepayID, true) 10 | //if err != nil { 11 | // panic(err) 12 | //} 13 | // 14 | //c.JSON(200, payConf) 15 | } 16 | -------------------------------------------------------------------------------- /controllers/wecom/jssdk.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func APITicketGet(c *gin.Context) { 10 | 11 | res, err := services.WeComApp.JSSDK.GetTicket(c.Request.Context()) 12 | 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | c.JSON(http.StatusOK, res) 18 | } 19 | -------------------------------------------------------------------------------- /controllers/wecom/base.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func GetCallbackIP(ctx *gin.Context) { 10 | data, err := services.WeComApp.Base.GetCallbackIP(ctx.Request.Context()) 11 | if err != nil { 12 | ctx.String(http.StatusBadRequest, err.Error()) 13 | return 14 | } 15 | ctx.JSON(http.StatusOK, data) 16 | } 17 | -------------------------------------------------------------------------------- /controllers/official-account/reply.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // AutoReply 获取当前设置的回复规则 10 | func AutoReplyCurrent(ctx *gin.Context) { 11 | data, err := services.OfficialAccountApp.AutoReply.Current(ctx.Request.Context()) 12 | if err != nil { 13 | ctx.String(http.StatusBadRequest, err.Error()) 14 | } 15 | ctx.JSON(http.StatusOK, data) 16 | } 17 | -------------------------------------------------------------------------------- /docs/md/official-account.getCallbackIp.md: -------------------------------------------------------------------------------- 1 | 2 | SDK产品接口的代码展示: 3 | ``` 4 | 返回类型定义如下: 5 | type ResponseGetCallBackIP struct { 6 | response.ResponseOfficialAccount 7 | 8 | IPList []string `json:"ip_list"` 9 | } 10 | 11 | 具体使用接口方式: 12 | func GetCallbackIP(ctx *gin.Context) { 13 | data, err := services.OfficialAccountApp.Base.GetCallbackIP(ctx.Request.Context()) 14 | if err != nil { 15 | ctx.String(http.StatusBadRequest, err.Error()) 16 | return 17 | } 18 | ctx.JSON(http.StatusOK, data) 19 | } 20 | ``` -------------------------------------------------------------------------------- /controllers/official-account/card.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func APIUpdate(ctx *gin.Context) { 10 | data, err := services.OfficialAccountApp.Card.Update(ctx.Request.Context(), 11 | "ph_gmt7cUVrlRk8swPwx7aDyF-pg", 12 | "member_card", 13 | nil, 14 | ) 15 | if err != nil { 16 | ctx.String(http.StatusBadRequest, err.Error()) 17 | return 18 | } 19 | ctx.JSON(http.StatusOK, data) 20 | } 21 | -------------------------------------------------------------------------------- /controllers/wecom/miniprogram.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func Code2Session(c *gin.Context) { 10 | code := c.DefaultQuery("code", "") 11 | 12 | miniProgramApp, err := services.WeComApp.MiniProgram() 13 | if err != nil { 14 | panic(err) 15 | } 16 | res, err := miniProgramApp.Auth.Session(c.Request.Context(), code) 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | c.JSON(http.StatusOK, res) 22 | } 23 | -------------------------------------------------------------------------------- /controllers/miniprogram/internet.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func APIInternetGetUserEncryptKey(c *gin.Context) { 10 | 11 | openID, exist := c.GetQuery("openID") 12 | if !exist { 13 | panic("parameter open id expected") 14 | } 15 | 16 | rs, err := services.MiniProgramApp.Internet.GetUserEncryptKey(c.Request.Context(), openID, "", "hmac_sha256") 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, rs) 23 | } 24 | -------------------------------------------------------------------------------- /config/model_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-playground/assert/v2" 6 | "github.com/jinzhu/configor" 7 | "log" 8 | "os" 9 | "testing" 10 | ) 11 | 12 | func TestGet(t *testing.T) { 13 | //os.Setenv("app_id", "wx_xxxxx") 14 | _ = os.Setenv("mch_id", "mch_xxx") 15 | var c Payment 16 | err := configor.Load(&c, "./model_test.yaml") 17 | if err != nil { 18 | log.Println(c) 19 | panic(err) 20 | } 21 | fmt.Printf("config: %#v\n", c) 22 | assert.Equal(t, c.AppID, "hello-app") 23 | assert.Equal(t, c.MchID, "mch_xxx") 24 | } -------------------------------------------------------------------------------- /docs/md/official-account.clearQuota.md: -------------------------------------------------------------------------------- 1 | 2 | SDK产品接口的代码展示: 3 | ``` 4 | 返回类型定义如下: 5 | type ResponseOfficialAccount struct { 6 | ResponseBase 7 | 8 | ErrCode int `json:"errcode,omitempty"` 9 | ErrMsg string `json:"errmsg,omitempty"` 10 | 11 | ResultCode string `json:"resultcode,omitempty"` 12 | ResultMsg string `json:"resultmsg,omitempty"` 13 | } 14 | 15 | 具体使用接口方式: 16 | func ClearQuota(ctx *gin.Context) { 17 | data, err := services.OfficialAccountApp.Base.ClearQuota(ctx.Request.Context()) 18 | if err != nil { 19 | ctx.String(http.StatusBadRequest, err.Error()) 20 | return 21 | } 22 | ctx.JSON(http.StatusOK, data) 23 | } 24 | ``` -------------------------------------------------------------------------------- /controllers/payment/apply4Sub/apply4Sub.go: -------------------------------------------------------------------------------- 1 | package apply4Sub 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/apply4Sub/request" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APIApplyFor(c *gin.Context) { 12 | //traceNo := c.Query("traceNo") 13 | //log.Printf("traceNo: %s", traceNo) 14 | 15 | para := &request.RequestApplyForBusiness{} 16 | 17 | rs, err := services.PaymentApp.Apply4Sub.ApplyForBusiness(c.Request.Context(), para) 18 | if err != nil { 19 | log.Println("出错了: ", err) 20 | c.String(400, err.Error()) 21 | return 22 | } 23 | c.JSON(http.StatusOK, rs) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /controllers/miniprogram/short-link.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // 获取小程序 Short Link 10 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/short-link/shortlink.generate.html 11 | func APIShortLinkGenerate(c *gin.Context) { 12 | 13 | pageUrl := c.DefaultQuery("pageUrl", "/pages/index/index?query1=q1") 14 | pageTitle := "Homework title" 15 | isPermanent := false 16 | 17 | rs, err := services.MiniProgramApp.ShortLink.Generate(c.Request.Context(), pageUrl, pageTitle, isPermanent) 18 | 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | c.JSON(http.StatusOK, rs) 24 | } 25 | -------------------------------------------------------------------------------- /controllers/wecom/oa/dial.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/dial/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 获取公费电话拨打记录 11 | // https://work.weixin.qq.com/api/doc/90000/90135/93662 12 | func APIDialGetDialRecord(c *gin.Context) { 13 | 14 | options := &request.RequestDialGetDialRecord{ 15 | MeetingID: 1536508800, 16 | Title: 1536940800, 17 | MeetingStart: 0, 18 | MeetingDuration: 100, 19 | } 20 | res, err := services.WeComApp.OADial.GetDialRecord(c.Request.Context(), options) 21 | 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | c.JSON(http.StatusOK, res) 27 | } 28 | -------------------------------------------------------------------------------- /controllers/payment/merchantService/merchantService.go: -------------------------------------------------------------------------------- 1 | package merchantService 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/merchantService/request" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APIComplaints(c *gin.Context) { 12 | 13 | para := &request.RequestComplaints{ 14 | Limit: 1, 15 | Offset: 10, 16 | BeginDate: "2024-01-01", 17 | EndDate: "2024-04-01", 18 | ComplaintedMchId: "1616273230", 19 | } 20 | 21 | rs, err := services.PaymentApp.MerchantService.Complaints(c.Request.Context(), para) 22 | if err != nil { 23 | log.Println("出错了: ", err) 24 | c.String(400, err.Error()) 25 | return 26 | } 27 | c.JSON(http.StatusOK, rs) 28 | 29 | } 30 | -------------------------------------------------------------------------------- /controllers/miniprogram/virtual-pay.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/virtualPayment/request" 5 | "github.com/gin-gonic/gin" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func APIStartUploadGoods(ctx *gin.Context) { 10 | 11 | params := &request.UploadProductsRequest{ 12 | Env: 0, 13 | UploadItem: []*request.GoodItem{ 14 | { 15 | Id: "18", 16 | Name: "1000K币", 17 | Price: 1000, 18 | Remake: "1000K币", 19 | ItemUrl: "https://qiniu.rongjuwh.cn/applet_kb_20230801101836.png", 20 | }, 21 | }, 22 | } 23 | 24 | rs, err := services.MiniProgramApp.VirtualPayment.StartUploadGoods(ctx.Request.Context(), params) 25 | 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | ctx.JSON(200, rs) 31 | } 32 | -------------------------------------------------------------------------------- /templates/openplatform-auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | auth 6 | 7 | 8 | 授权微信小程序 9 | 授权微信公众号 10 | 11 | 12 | -------------------------------------------------------------------------------- /controllers/wecom/oa/pstncc.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func APIPSTNCCCall(c *gin.Context) { 10 | options := []string{ 11 | c.DefaultQuery("userID", "matrix-x"), 12 | } 13 | 14 | res, err := services.WeComApp.OAPSTNCC.Call(c.Request.Context(), options) 15 | 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | c.JSON(http.StatusOK, res) 21 | } 22 | 23 | func APIPSTNCCGetStates(c *gin.Context) { 24 | 25 | calleeUserID := c.DefaultQuery("calleeUserID", "matrix-x") 26 | callID := c.DefaultQuery("userID", "matrix-x") 27 | 28 | res, err := services.WeComApp.OAPSTNCC.GetStates(c.Request.Context(), calleeUserID, callID) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | c.JSON(http.StatusOK, res) 35 | } 36 | -------------------------------------------------------------------------------- /controllers/miniprogram/url-link.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/urlLink/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 获取小程序 URL Link,适用于短信、邮件、网页、微信内等拉起小程序的业务场景 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-link/urllink.generate.html 12 | func APIURLLinkGenerate(c *gin.Context) { 13 | 14 | path := c.DefaultQuery("path", "pages/index/index") 15 | 16 | rs, err := services.MiniProgramApp.URLLink.Generate(c.Request.Context(), 17 | &request.URLLinkGenerate{ 18 | EnvVersion: "release", 19 | ExpireInterval: 1606737600, 20 | Path: path, 21 | Query: "a=1", 22 | }) 23 | 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | c.JSON(http.StatusOK, rs) 29 | } 30 | -------------------------------------------------------------------------------- /controllers/official-account/short-url.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // ShortGenKey 短key托管 10 | func ShortGenKey(ctx *gin.Context) { 11 | longData := ctx.DefaultQuery("longData", "longData test.....") 12 | data, err := services.OfficialAccountApp.URL.ShortGenKey(ctx.Request.Context(), longData, 30*24*3600) 13 | if err != nil { 14 | ctx.String(http.StatusBadRequest, err.Error()) 15 | } 16 | ctx.JSON(http.StatusOK, data) 17 | } 18 | 19 | // FetchShortGen 短key还原 20 | func FetchShortGen(ctx *gin.Context) { 21 | shortKey := ctx.Query("shortKey") 22 | data, err := services.OfficialAccountApp.URL.FetchShorten(ctx.Request.Context(), shortKey) 23 | if err != nil { 24 | ctx.String(http.StatusBadRequest, err.Error()) 25 | } 26 | ctx.JSON(http.StatusOK, data) 27 | } 28 | -------------------------------------------------------------------------------- /controllers/miniprogram/phone-number.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | func GetUserPhoneNumber(c *gin.Context) { 10 | 11 | code, exist := c.GetQuery("code") 12 | if !exist { 13 | panic("parameter code expected") 14 | } 15 | 16 | rs, err := services.MiniProgramApp.PhoneNumber.GetUserPhoneNumber(c.Request.Context(), code) 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, rs) 23 | } 24 | 25 | func GetUserPhoneNumberByAES(c *gin.Context) { 26 | encryptData := c.Query("encryptData") 27 | sessionKey := c.Query("sessionKey") 28 | iv := c.Query("iv") 29 | 30 | data, err := services.MiniProgramApp.Encryptor.DecryptData(encryptData, sessionKey, iv) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | c.String(200, string(data)) 36 | } 37 | -------------------------------------------------------------------------------- /controllers/wecom/user/validate/user-create.go: -------------------------------------------------------------------------------- 1 | package validate 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/object" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/user/request" 6 | "github.com/gin-gonic/gin" 7 | "power-wechat-tutorial/config" 8 | http "power-wechat-tutorial/controllers" 9 | ) 10 | 11 | func ValidateUserCreate(context *gin.Context) { 12 | var form request.RequestUserDetail 13 | 14 | if err := context.ShouldBind(&form); err != nil { 15 | if err := context.ShouldBindJSON(&form); err != nil { 16 | 17 | apiResponse := http.NewAPIResponse(context) 18 | apiResponse.SetCode( 19 | config.API_ERR_CODE_REQUEST_PARAM_ERROR, 20 | config.API_RETURN_CODE_ERROR, 21 | "", "").SetData(object.HashMap{ 22 | "chat-bot": err.Error(), 23 | }).ThrowJSONResponse(context) 24 | } 25 | } 26 | 27 | context.Set("params", &form) 28 | context.Next() 29 | } 30 | -------------------------------------------------------------------------------- /controllers/payment/bill.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "os" 8 | "path" 9 | "power-wechat-tutorial/services" 10 | ) 11 | 12 | // 下载账单API为通用接口,交易/资金账单都可以通过该接口获取到对应的账单 13 | // https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter3_1_8.shtml 14 | func APIBillDownloadURL(c *gin.Context) { 15 | 16 | requestDownload := &power.RequestDownload{ 17 | HashType: "SHA1", 18 | HashValue: "442c2363a7e014b7f7cf3e2c558375bcf385951d", 19 | DownloadURL: "https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg", 20 | } 21 | homePath, _ := os.UserHomeDir() 22 | filePath := path.Join(homePath, "Desktop/download-url") 23 | 24 | rs, err := services.PaymentApp.Bill.DownloadBill(c.Request.Context(), requestDownload, filePath) 25 | if err != nil { 26 | panic(err) 27 | } 28 | c.JSON(http.StatusOK, rs) 29 | 30 | } 31 | -------------------------------------------------------------------------------- /controllers/official-account/qrcode.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // 创建临时二维码 10 | func GetTempQrCode(ctx *gin.Context) { 11 | data, err := services.OfficialAccountApp.QRCode.Temporary(ctx.Request.Context(), "val1", 30*24*3600) 12 | if err != nil { 13 | ctx.String(http.StatusBadRequest, err.Error()) 14 | return 15 | } 16 | ctx.JSON(http.StatusOK, data) 17 | } 18 | 19 | // GetForeverQrCode 创建永久二维码 20 | func GetForeverQrCode(ctx *gin.Context) { 21 | data, err := services.OfficialAccountApp.QRCode.Forever(ctx.Request.Context(), "val1") 22 | if err != nil { 23 | ctx.String(http.StatusBadRequest, err.Error()) 24 | return 25 | } 26 | ctx.JSON(http.StatusOK, data) 27 | } 28 | 29 | // 获取二维码网址 30 | func GetQrCodeUrl(ctx *gin.Context) { 31 | url := services.OfficialAccountApp.QRCode.URL("from") 32 | ctx.String(http.StatusOK, url) 33 | } 34 | -------------------------------------------------------------------------------- /services/get-env.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | const LOCALE_EN = "en_US" 10 | const LOCALE_CN = "zh_CN" 11 | const LOCALE_TW = "zh_TW" 12 | 13 | var ErrEnvVarEmpty = errors.New("getEnv: environment variable empty") 14 | 15 | func getEnvStr(key string) (string, error) { 16 | v := os.Getenv(key) 17 | if v == "" { 18 | return v, ErrEnvVarEmpty 19 | } 20 | return v, nil 21 | } 22 | 23 | func getEnvInt(key string) (int, error) { 24 | s, err := getEnvStr(key) 25 | if err != nil { 26 | return 0, err 27 | } 28 | v, err := strconv.Atoi(s) 29 | if err != nil { 30 | return 0, err 31 | } 32 | return v, nil 33 | } 34 | 35 | func getEnvBool(key string) (bool, error) { 36 | s, err := getEnvStr(key) 37 | if err != nil { 38 | return false, err 39 | } 40 | v, err := strconv.ParseBool(s) 41 | if err != nil { 42 | return false, err 43 | } 44 | return v, nil 45 | } 46 | -------------------------------------------------------------------------------- /controllers/miniprogram/soter.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/soter/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // SOTER 生物认证秘钥签名验证 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/soter/soter.verifySignature.html 12 | func APISoterVerifySignature(c *gin.Context) { 13 | 14 | jsonString := c.Query("jsonString") 15 | jsonSignature := c.Query("jsonSignature") 16 | 17 | openID, exist := c.GetQuery("openID") 18 | if !exist { 19 | panic("parameter open id expected") 20 | } 21 | 22 | rs, err := services.MiniProgramApp.Soter.VerifySignature( 23 | c.Request.Context(), 24 | &request.RequestSoter{ 25 | OpenID: openID, 26 | JsonString: jsonString, 27 | JsonSignature: jsonSignature, 28 | }) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | c.JSON(http.StatusOK, rs) 35 | } 36 | -------------------------------------------------------------------------------- /controllers/wecom/oa/approval.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/approval/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | func APIApprovalOaGetTemplateDetail(c *gin.Context) { 11 | 12 | } 13 | 14 | func APIApprovalOaGetApprovalInfo(c *gin.Context) { 15 | 16 | } 17 | 18 | func APIApprovalOaGetApprovalDetail(c *gin.Context) { 19 | 20 | } 21 | 22 | func APIApprovalOaGetApprovalData(c *gin.Context) { 23 | 24 | } 25 | 26 | func APIApprovalVacationGetCorpConf(c *gin.Context) { 27 | 28 | } 29 | 30 | func APIApprovalVacationGetUserVacationQuota(c *gin.Context) { 31 | 32 | } 33 | 34 | func APIApprovalVacationSetOneUserQuota(c *gin.Context) { 35 | 36 | } 37 | 38 | func APIApprovalUpdateTemplate(c *gin.Context) { 39 | options := &request.RequestUpdateTemplate{} 40 | res, err := services.WeComApp.OAApproval.UpdateTemplate(c.Request.Context(), options) 41 | 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | c.JSON(http.StatusOK, res) 47 | } 48 | -------------------------------------------------------------------------------- /controllers/payment/tax/tax.go: -------------------------------------------------------------------------------- 1 | package tax 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/tax/request" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APIApplyForCardTemplate(c *gin.Context) { 12 | //traceNo := c.Query("traceNo") 13 | //log.Printf("traceNo: %s", traceNo) 14 | 15 | para := &request.RequestApplyForCardTemplate{ 16 | CardAppid: "wxa5fa5b1adab7aa1f", 17 | CardTemplateInformation: &request.CardTemplateInformation{ 18 | PayeeName: "123", 19 | LogoUrl: "123", 20 | CustomCell: &request.CustomCell{ 21 | Words: "123", 22 | Description: "321", 23 | JumpUrl: "3213", 24 | MiniProgramUserName: "1231", 25 | MiniProgramPath: "123", 26 | }, 27 | }, 28 | } 29 | 30 | rs, err := services.PaymentApp.Tax.ApplyForCardTemplate(c.Request.Context(), para) 31 | if err != nil { 32 | log.Println("出错了: ", err) 33 | c.String(400, err.Error()) 34 | return 35 | } 36 | c.JSON(http.StatusOK, rs) 37 | 38 | } 39 | -------------------------------------------------------------------------------- /controllers/payment/paymentScore/paymentScore.go: -------------------------------------------------------------------------------- 1 | package paymentScore 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/payScore/request" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APIServiceOrder(c *gin.Context) { 12 | //traceNo := c.Query("traceNo") 13 | //log.Printf("traceNo: %s", traceNo) 14 | 15 | para := &request.RequestServiceOrder{ 16 | OutOrderNo: "", 17 | Appid: "", 18 | ServiceId: "", 19 | ServiceIntroduction: "", 20 | PostPayments: nil, 21 | PostDiscounts: nil, 22 | //TimeRange: nil, 23 | //Location: nil, 24 | //RiskFund: nil, 25 | Attach: "", 26 | NotifyUrl: "", 27 | Openid: "", 28 | //NeedUserConfirm: nil, 29 | } 30 | 31 | rs, err := services.PaymentApp.PayScore.ServiceOrder(c.Request.Context(), para) 32 | if err != nil { 33 | log.Println("出错了: ", err) 34 | c.String(400, err.Error()) 35 | return 36 | } 37 | c.JSON(http.StatusOK, rs) 38 | 39 | } 40 | -------------------------------------------------------------------------------- /controllers/payment/merchant/merchant.go: -------------------------------------------------------------------------------- 1 | package merchant 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "fmt" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/merchant/request" 8 | "github.com/gin-gonic/gin" 9 | "log" 10 | "net/http" 11 | "os" 12 | "path" 13 | "power-wechat-tutorial/services" 14 | ) 15 | 16 | func APIUploadImg(c *gin.Context) { 17 | 18 | // 读取图片文件 19 | homePath, _ := os.UserHomeDir() 20 | imagePath := path.Join(homePath, "Desktop/641.png") 21 | imageData, err := os.ReadFile(imagePath) 22 | if err != nil { 23 | fmt.Println("无法读取图片文件:", err) 24 | os.Exit(1) 25 | } 26 | 27 | hash := sha256.Sum256(imageData) 28 | hashString := hex.EncodeToString(hash[:]) 29 | 30 | para := &request.RequestMediaUpload{ 31 | File: imagePath, 32 | Meta: &request.Meta{ 33 | Filename: "641.png", 34 | Sha256: hashString, 35 | }, 36 | } 37 | 38 | rs, err := services.PaymentApp.Merchant.UploadImg(c.Request.Context(), para) 39 | if err != nil { 40 | log.Println("出错了: ", err) 41 | c.String(400, err.Error()) 42 | return 43 | } 44 | c.JSON(http.StatusOK, rs) 45 | 46 | } 47 | -------------------------------------------------------------------------------- /services/open-platform.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/openPlatform" 6 | "power-wechat-tutorial/config" 7 | ) 8 | 9 | var OpenPlatformApp *openPlatform.OpenPlatform 10 | 11 | func NewOpenPlatformAppService(conf *config.Configuration) (*openPlatform.OpenPlatform, error) { 12 | 13 | var cache kernel.CacheInterface 14 | if conf.MiniProgram.RedisAddr != "" { 15 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 16 | Addrs: []string{conf.MiniProgram.RedisAddr}, 17 | }) 18 | } 19 | 20 | app, err := openPlatform.NewOpenPlatform(&openPlatform.UserConfig{ 21 | 22 | AppID: conf.OpenPlatform.AppID, 23 | Secret: conf.OpenPlatform.AppSecret, 24 | 25 | Token: conf.OpenPlatform.MessageToken, 26 | AESKey: conf.OpenPlatform.MessageAesKey, 27 | 28 | //Log: openPlatform.Log{ 29 | // Level: "debug", 30 | // File: "./wechat.log", 31 | //}, 32 | Cache: cache, 33 | HttpDebug: false, 34 | Debug: false, 35 | Http: openPlatform.Http{ 36 | Timeout: 30, 37 | }, 38 | //"sandbox": true, 39 | }) 40 | 41 | return app, err 42 | } 43 | -------------------------------------------------------------------------------- /controllers/miniprogram/risk-control.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/riskControl/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 根据提交的用户信息数据获取用户的安全等级 risk_rank,无需用户授权。 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/safety-control-capability/riskControl.getUserRiskRank.html 12 | func APIRiskControlGetUserRiskRank(c *gin.Context) { 13 | 14 | appID, exist := c.GetQuery("appID") 15 | if !exist { 16 | panic("parameter app id expected") 17 | } 18 | 19 | openID, exist := c.GetQuery("openID") 20 | if !exist { 21 | panic("parameter open id expected") 22 | } 23 | 24 | options := &request.RequestRiskControl{ 25 | AppID: appID, 26 | OpenID: openID, 27 | Scene: 1, 28 | MobileNo: "12345678", 29 | BankCardNo: "******", 30 | CertNo: "*******", 31 | ClientIP: "******", 32 | EmailAddress: "***@qq.com", 33 | ExtendedInfo: "", 34 | } 35 | 36 | rs, err := services.MiniProgramApp.RiskControl.GetUserRiskRank(c.Request.Context(), options) 37 | 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | c.JSON(http.StatusOK, rs) 43 | } 44 | -------------------------------------------------------------------------------- /controllers/official-account/base.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // ClearQuota godoc 10 | // @Summary 公众号里清空api的调用quota:https://developers.weixin.qq.com/doc/offiaccount/openApi/clear_quota.html 11 | // @Description.markdown official-account.clearQuota 12 | // @Tags OfficialAccount.base.ClearQuota 13 | // @Router /clearQuota [get] 14 | func ClearQuota(ctx *gin.Context) { 15 | data, err := services.OfficialAccountApp.Base.ClearQuota(ctx.Request.Context()) 16 | if err != nil { 17 | ctx.String(http.StatusBadRequest, err.Error()) 18 | return 19 | } 20 | ctx.JSON(http.StatusOK, data) 21 | } 22 | 23 | // GetCallbackIP godoc 24 | // @Summary 获取公众号回调的IP地址:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html 25 | // @Description.markdown official-account.getCallbackIp 26 | // @Tags OfficialAccount.base.GetCallbackIP 27 | // @Router /getCallbackIp [get] 28 | func GetCallbackIP(ctx *gin.Context) { 29 | data, err := services.OfficialAccountApp.Base.GetCallbackIP(ctx.Request.Context()) 30 | if err != nil { 31 | ctx.String(http.StatusBadRequest, err.Error()) 32 | return 33 | } 34 | ctx.JSON(http.StatusOK, data) 35 | } 36 | -------------------------------------------------------------------------------- /services/offiaccount-service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount" 6 | "os" 7 | "power-wechat-tutorial/config" 8 | ) 9 | 10 | var OfficialAccountApp *officialAccount.OfficialAccount 11 | 12 | func NewOfficialAccountAppService(conf *config.Configuration) (*officialAccount.OfficialAccount, error) { 13 | 14 | var cache kernel.CacheInterface 15 | if conf.MiniProgram.RedisAddr != "" { 16 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 17 | Addrs: []string{conf.MiniProgram.RedisAddr}, 18 | }) 19 | } 20 | 21 | app, err := officialAccount.NewOfficialAccount(&officialAccount.UserConfig{ 22 | 23 | AppID: conf.OffiAccount.AppID, // 小程序、公众号或者企业微信的appid 24 | Secret: conf.OffiAccount.AppSecret, // 商户号 appID 25 | 26 | Token: conf.OffiAccount.MessageToken, 27 | AESKey: conf.OffiAccount.MessageAesKey, 28 | ResponseType: os.Getenv("response_type"), 29 | Log: officialAccount.Log{ 30 | //Driver: &testLogDriver.SimpleLogger{}, 31 | Level: "debug", 32 | //File: "./wechat.log", 33 | Stdout: false, 34 | }, 35 | Cache: cache, 36 | HttpDebug: true, 37 | Debug: false, 38 | //"sandbox": true, 39 | }) 40 | 41 | return app, err 42 | } 43 | -------------------------------------------------------------------------------- /controllers/wecom/user/message.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // SendTextMsg 企业微信内部应用主动发送消息 11 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90236 12 | func APISendTextMsg(c *gin.Context) { 13 | toUser := c.DefaultQuery("toUser", "walle") 14 | res, err := services.WeComApp.Message.Send(c.Request.Context(), &power.HashMap{ 15 | "touser": toUser, 16 | "msgtype": "text", 17 | "agentid": 1000004, 18 | "text": &power.StringMap{ 19 | "content": "你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。", 20 | }, 21 | "safe": 0, 22 | "enable_id_trans": 0, 23 | "enable_duplicate_check": 0, 24 | "duplicate_check_interval": 1800, 25 | }) 26 | 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | c.JSON(http.StatusOK, res) 32 | } 33 | 34 | // Recall 撤回应用消息 35 | // https://open.work.weixin.qq.com/api/doc/90000/90135/94867 36 | func APIRecallMsg(c *gin.Context) { 37 | msgID := c.Query("msgID") 38 | res, err := services.WeComApp.Message.Recall(c.Request.Context(), msgID) 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | c.JSON(http.StatusOK, res) 45 | } 46 | -------------------------------------------------------------------------------- /controllers/miniprogram/url-scheme.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/urlScheme/request" 5 | "github.com/gin-gonic/gin" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // 获取小程序 scheme 码,适用于短信、邮件、外部网页、微信内等拉起小程序的业务场景 10 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/url-scheme/urlscheme.generate.html 11 | func APIURLSchemeGenerate(c *gin.Context) { 12 | 13 | path, exist := c.GetQuery("path") 14 | if !exist { 15 | panic("parameter path expected") 16 | } 17 | 18 | rs, err := services.MiniProgramApp.URLScheme.Generate(c.Request.Context(), 19 | &request.URLSchemeGenerate{ 20 | JumpWxa: &request.JumpWxa{ 21 | Path: path, 22 | Query: "b=2&c=2", 23 | }, 24 | IsExpire: true, 25 | ExpireType: 1, 26 | ExpireTime: 1606737600, 27 | ExpireInterval: 30, 28 | }) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | //content, _ := ioutil.ReadAll(rs.Body) 35 | ////fmt.Dump("content-type:",rs.Header.Get("Content-Type")) 36 | //c.Header("Content-Type", rs.Header.Get("Content-Type")) 37 | //c.Header("Content-Disposition", rs.Header.Get("attachment;filename=\""+rs.Header.Get("filename")+"\"")) 38 | //c.Data(http.StatusOK, rs.Header.Get("Content-Type"), content) 39 | c.JSON(200, rs) 40 | } 41 | -------------------------------------------------------------------------------- /controllers/wecom/account-service/service-state.go: -------------------------------------------------------------------------------- 1 | package account_service 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | "strconv" 8 | ) 9 | 10 | // 获取会话状态 11 | // https://work.weixin.qq.com/api/doc/90000/90135/94669 12 | func APIAccountServiceStateGet(c *gin.Context) { 13 | 14 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 15 | externalUserID := c.DefaultQuery("externalUserID", "wmxxxxxxxxxxxxxxxxxx") 16 | 17 | res, err := services.WeComApp.AccountServiceState.Get(c.Request.Context(), openKFID, externalUserID) 18 | 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | c.JSON(http.StatusOK, res) 24 | } 25 | 26 | // 变更会话状态 27 | // https://work.weixin.qq.com/api/doc/90000/90135/94669 28 | func APIAccountServiceStateTrans(c *gin.Context) { 29 | 30 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 31 | externalUserID := c.DefaultQuery("externalUserID", "kfxxxxxxxxxxxxxx") 32 | serviceState := c.DefaultQuery("serviceState", "kfxxxxxxxxxxxxxx") 33 | servicerUserID := c.DefaultQuery("servicerUserID", "kfxxxxxxxxxxxxxx") 34 | 35 | state, _ := strconv.Atoi(serviceState) 36 | 37 | res, err := services.WeComApp.AccountServiceState.Trans(c.Request.Context(), openKFID, externalUserID, state, servicerUserID) 38 | 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | c.JSON(http.StatusOK, res) 44 | } 45 | -------------------------------------------------------------------------------- /controllers/official-account/oauth.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/gin-gonic/gin" 6 | "log" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func GetAuthCode(ctx *gin.Context) { 12 | 13 | //result, err := services.OfficialAccountApp.JSSDK.ConfigSignature(ctx, "text", "", 0) 14 | //if err != nil { 15 | // panic(err.Error()) 16 | //} 17 | //ctx.JSON(http.StatusOK, gin.H{"result": result}) 18 | //return 19 | code := ctx.Query("code") 20 | state := ctx.Query("state") 21 | 22 | ctx.JSON(http.StatusOK, gin.H{"code": code, "state": state}) 23 | } 24 | 25 | func UserFromCode(ctx *gin.Context) { 26 | code := ctx.Query("code") 27 | services.OfficialAccountApp.OAuth.SetScopes([]string{"snsapi_base"}) 28 | user, err := services.OfficialAccountApp.OAuth.UserFromCode(code) 29 | if err != nil { 30 | ctx.String(http.StatusBadRequest, err.Error()) 31 | return 32 | } 33 | ctx.JSON(http.StatusOK, user) 34 | } 35 | 36 | func UserFromToken(ctx *gin.Context) { 37 | token := ctx.Query("token") 38 | openID := ctx.Query("openID") 39 | user, err := services.OfficialAccountApp.OAuth.UserFromToken(token, openID) 40 | rsToken := user.GetTokenResponse() 41 | fmt.Dump(rsToken, (*rsToken)["openid"]) 42 | log.Println(err) 43 | if err != nil { 44 | ctx.String(http.StatusBadRequest, err.Error()) 45 | return 46 | } 47 | ctx.JSON(http.StatusOK, user) 48 | } 49 | -------------------------------------------------------------------------------- /controllers/payment/redpack/redpack.go: -------------------------------------------------------------------------------- 1 | package redpack 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/redpack/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APISendNormal(c *gin.Context) { 12 | 13 | mchId := services.PaymentApp.GetConfig().GetString("mch_id", "") 14 | appId := services.PaymentApp.GetConfig().GetString("app_id", "") 15 | 16 | options := &request.RequestSendRedPack{ 17 | MchBillNO: "0010010404201411170000046545", 18 | MchID: mchId, 19 | WXAppID: appId, 20 | SendName: "ArtisanCloud", 21 | ReOpenID: "oAuaP0TRUMwP169nQfg7XCEAw3HQ", 22 | TotalAmount: 100, 23 | TotalNum: 1, 24 | Wishing: "恭喜发财", 25 | ClientIP: "127.0.0.1", 26 | ActName: "新年红包", 27 | Remark: "新年红包", 28 | SceneID: "PRODUCT_2", 29 | RiskInfo: "posttime%3d123123412%26clientversion%3d234134%26mobile%3d122344545%26deviceid%3dIOS", 30 | } 31 | 32 | payConf, err := services.PaymentApp.RedPack.SendNormal(c.Request.Context(), options) 33 | if err != nil { 34 | fmt.Dump(err.Error()) 35 | panic(err) 36 | } 37 | 38 | c.XML(http.StatusOK, payConf) 39 | } 40 | 41 | func APIQueryRedPack(c *gin.Context) { 42 | rs, err := services.PaymentApp.RedPack.Info(c.Request.Context(), "0010010404201411170000046545") 43 | if err != nil { 44 | panic(nil) 45 | } 46 | 47 | c.JSON(http.StatusOK, rs) 48 | } 49 | -------------------------------------------------------------------------------- /controllers/wecom/corpgroup.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | "strconv" 8 | ) 9 | 10 | // 获取应用共享信息 11 | // https://open.work.weixin.qq.com/api/doc/90000/90135/93403 12 | func APICorpGroupCorpListAppShareInfo(c *gin.Context) { 13 | 14 | agentId := c.DefaultQuery("agentID", "") 15 | agentID, _ := strconv.Atoi(agentId) 16 | 17 | res, err := services.WeComApp.CorpGroup.GetAppShareInfo(c.Request.Context(), agentID) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, res) 23 | } 24 | 25 | // 获取下级企业的access_token 26 | // https://open.work.weixin.qq.com/api/doc/90000/90135/93359 27 | func APICorpGroupCorpGetToken(c *gin.Context) { 28 | 29 | corpID := c.DefaultQuery("corpID", "") 30 | agentID := c.DefaultQuery("agentID", "") 31 | res, err := services.WeComApp.CorpGroup.GetToken(c.Request.Context(), corpID, agentID) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, res) 38 | } 39 | 40 | // 获取下级企业的小程序session 41 | // https://open.work.weixin.qq.com/api/doc/90000/90135/93355 42 | func APICorpGroupMiniProgramTransferSession(c *gin.Context) { 43 | 44 | userID := c.DefaultQuery("userID", "matrix-x") 45 | sessionKey := "" 46 | res, err := services.WeComApp.CorpGroup.GetMiniProgramTransferSession(c.Request.Context(), userID, sessionKey) 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | c.JSON(http.StatusOK, res) 53 | } 54 | -------------------------------------------------------------------------------- /controllers/miniprogram/service-market.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/serviceMarket/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 调用服务平台提供的服务 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/service-market/serviceMarket.invokeService.html 13 | func APIServiceMarketInvokeService(c *gin.Context) { 14 | 15 | serviceID, exist := c.GetQuery("serviceID") 16 | if !exist { 17 | panic("parameter service id expected") 18 | } 19 | 20 | apiName, exist := c.GetQuery("apiName") 21 | if !exist { 22 | panic("parameter api name expected") 23 | } 24 | 25 | clientMsgID, exist := c.GetQuery("clientMsgID") 26 | if !exist { 27 | panic("parameter client msg id expected") 28 | } 29 | 30 | serviceData := &power.HashMap{ 31 | "img_url": "http://mmbiz.qpic.cn/mmbiz_jpg/7UFjuNbYxibu66xSqsQqKcuoGBZM77HIyibdiczeWibdMeA2XMt5oibWVQMgDibriazJSOibLqZxcO6DVVcZMxDKgeAtbQ/0", 32 | "data_type": 3, 33 | "ocr_type": 1, 34 | } 35 | 36 | rs, err := services.MiniProgramApp.ServiceMarket.InvokeService(c.Request.Context(), 37 | &request.RequestServiceMarket{ 38 | Service: serviceID, 39 | Api: apiName, 40 | ClientMsgID: clientMsgID, 41 | Data: serviceData, 42 | }) 43 | 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | c.JSON(http.StatusOK, rs) 49 | } 50 | -------------------------------------------------------------------------------- /controllers/wecom/account-service/servicer.go: -------------------------------------------------------------------------------- 1 | package account_service 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // 添加接待人员 10 | // https://work.weixin.qq.com/api/doc/90000/90135/94646 11 | func APIAccountServiceServicerAdd(c *gin.Context) { 12 | 13 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 14 | userIDList := []string{c.DefaultQuery("userIDList", "matrix-x")} 15 | 16 | res, err := services.WeComApp.AccountServiceServicer.Add(c.Request.Context(), openKFID, userIDList) 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, res) 23 | } 24 | 25 | // 删除接待人员 26 | // https://work.weixin.qq.com/api/doc/90000/90135/94647 27 | func APIAccountServiceServicerDel(c *gin.Context) { 28 | 29 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 30 | userIDList := []string{c.DefaultQuery("userIDList", "matrix-x")} 31 | 32 | res, err := services.WeComApp.AccountServiceServicer.Del(c.Request.Context(), openKFID, userIDList) 33 | 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | c.JSON(http.StatusOK, res) 39 | } 40 | 41 | // 获取接待人员列表 42 | // https://work.weixin.qq.com/api/doc/90000/90135/94645 43 | func APIAccountServiceServicerList(c *gin.Context) { 44 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 45 | 46 | res, err := services.WeComApp.AccountServiceServicer.List(c.Request.Context(), openKFID) 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | c.JSON(http.StatusOK, res) 53 | } 54 | -------------------------------------------------------------------------------- /controllers/wecom/external-contact/statistic.go: -------------------------------------------------------------------------------- 1 | package external_contact 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/externalContact/statistics/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 获取「联系客户统计」数据 12 | // https://work.weixin.qq.com/api/doc/90000/90135/92132 13 | func APIExternalContactGetUserBehaviorData(c *gin.Context) { 14 | 15 | options := &request.RequestGetUserBehaviorData{ 16 | UserID: []string{c.DefaultQuery("userID", "matrix-x")}, 17 | PartyID: []int{1001}, 18 | StartTime: 1536508800, 19 | EndTime: 1536595200, 20 | } 21 | 22 | res, err := services.WeComContactApp.ExternalContactStatistics.GetUserBehaviorData(c.Request.Context(), options) 23 | 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | c.JSON(http.StatusOK, res) 29 | } 30 | 31 | // 获取「群聊数据统计」数据 32 | // https://work.weixin.qq.com/api/doc/90000/90135/92133 33 | func APIExternalContactGroupChatStatistic(c *gin.Context) { 34 | options := &request.RequestStatistic{ 35 | DayBeginTime: 1600272000, 36 | DayEndTime: 1600444800, 37 | OwnerFilter: &power.HashMap{ 38 | "userid_list": []string{c.DefaultQuery("userID", "matrix-x")}, 39 | }, 40 | OrderBy: 2, 41 | OrderAsc: 0, 42 | Offset: 0, 43 | Limit: 1000, 44 | } 45 | res, err := services.WeComContactApp.ExternalContactStatistics.Statistic(c.Request.Context(), options) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | c.JSON(http.StatusOK, res) 52 | } 53 | -------------------------------------------------------------------------------- /routes/router.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-contrib/cors" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | var Router *gin.Engine 12 | 13 | func InitializeRoutes(r *gin.Engine) { 14 | 15 | r.Use(cors.New(cors.Config{ 16 | AllowOrigins: []string{"*"}, 17 | AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 18 | AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-Requested-With"}, 19 | AllowCredentials: true, 20 | AllowOriginFunc: func(origin string) bool { 21 | log.Println("origin: ", origin) 22 | return true 23 | }, 24 | })) 25 | 26 | // Payment App Router 27 | InitPaymentAPIRoutes(r) 28 | 29 | // MiniProgram App Router 30 | InitMiniProgramAPIRoutes(r) 31 | 32 | // WeCom App Router 33 | InitWecomAPIRoutes(r) 34 | 35 | // Official App Router 36 | InitOfficialAPIRoutes(r) 37 | 38 | // OpenPlatform App Router 39 | InitOpenPlatformAPIRoutes(r) 40 | 41 | r.GET("/", func(c *gin.Context) { 42 | //c.String(200, "hello") 43 | c.Writer.WriteHeader(http.StatusOK) 44 | c.Writer.Write([]byte("Hello, PowerWechat")) 45 | 46 | }) 47 | 48 | r.GET("/json/user", func(context *gin.Context) { 49 | obj := &power.HashMap{ 50 | "say": "I am", 51 | "something": "ArtisanCloud", 52 | } 53 | context.JSON(http.StatusOK, obj) 54 | }) 55 | 56 | r.LoadHTMLGlob("templates/openplatform-auth.html") 57 | r.GET("/openplatform-auth", func(c *gin.Context) { 58 | c.HTML(http.StatusOK, "openplatform-auth.html", nil) 59 | }) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /controllers/payment/security.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/support" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/security/response" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "power-wechat-tutorial/services" 10 | ) 11 | 12 | // Get RSA Public Key. 13 | // https://pay.weixin.qq.com/wiki/doc/api/tools/mch_pay_yhk.php?chapter=25_7&index=4 14 | func APIGetRSAPublicKey(c *gin.Context) { 15 | 16 | rs, err := services.PaymentApp.Security.GetRSAPublicKey(c.Request.Context()) 17 | if err != nil { 18 | panic(err) 19 | } 20 | fmt.Dump(rs.PubKey) 21 | 22 | c.JSON(http.StatusOK, rs) 23 | 24 | } 25 | 26 | // 获取平台证书 27 | // https://pay.weixin.qq.com/wiki/doc/apiv3/apis/wechatpay5_1.shtml 28 | func APIGetCertificates(c *gin.Context) { 29 | 30 | rs, err := services.PaymentApp.Security.GetCertificates(c.Request.Context()) 31 | if err != nil { 32 | panic(err) 33 | } 34 | c.JSON(http.StatusOK, rs) 35 | 36 | } 37 | 38 | func APIDecryptCertificate(c *gin.Context) { 39 | 40 | form := &response.ResponseGetCertificates{} 41 | 42 | if err := c.ShouldBindJSON(form); err != nil { 43 | panic(err) 44 | } 45 | 46 | config := services.PaymentApp.GetConfig() 47 | v3AESKey := config.GetString("mch_api_v3_key", "") 48 | 49 | plainTXT, err := support.DecryptAES256GCM( 50 | v3AESKey, 51 | form.Data[0].EncryptCertificate.AssociatedData, 52 | form.Data[0].EncryptCertificate.Nonce, 53 | form.Data[0].EncryptCertificate.Ciphertext, 54 | ) 55 | if err != nil { 56 | panic(err) 57 | } 58 | fmt.Dump(plainTXT) 59 | c.JSON(http.StatusOK, plainTXT) 60 | 61 | } 62 | -------------------------------------------------------------------------------- /controllers/miniprogram/updatable-message.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/updatableMessage/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 创建被分享动态消息或私密消息的 activity_id 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/updatable-message/updatableMessage.createActivityId.html 12 | func APIUpdatableMessageCreateActivityID(c *gin.Context) { 13 | 14 | openID, exist := c.GetQuery("openID") 15 | if !exist { 16 | panic("parameter open id expected") 17 | } 18 | 19 | rs, err := services.MiniProgramApp.UpdatableMessage.CreateActivityID(c.Request.Context(), "", openID) 20 | 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | c.JSON(http.StatusOK, rs) 26 | } 27 | 28 | // 修改被分享的动态消息。 29 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/updatable-message/updatableMessage.setUpdatableMsg.html 30 | func APIUpdatableMessageUpdatableMessage(c *gin.Context) { 31 | 32 | activityID, exist := c.GetQuery("activityID") 33 | if !exist { 34 | panic("parameter open id expected") 35 | } 36 | 37 | rs, err := services.MiniProgramApp.UpdatableMessage.SetUpdatableMsg(c.Request.Context(), &request.RequestSetUpdatableMsg{ 38 | ActivityID: activityID, 39 | TargetState: 0, 40 | TemplateInfo: &request.TemplateInfo{ 41 | ParameterList: []*request.ParameterListItem{ 42 | { 43 | Name: "member_count", 44 | Value: "2", 45 | }, 46 | { 47 | Name: "room_limit", 48 | Value: "5", 49 | }, 50 | }, 51 | }, 52 | }) 53 | 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | c.JSON(http.StatusOK, rs) 59 | } 60 | -------------------------------------------------------------------------------- /services/miniprogram-service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/logger/drivers" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/response" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram" 8 | "power-wechat-tutorial/config" 9 | ) 10 | 11 | var MiniProgramApp *miniProgram.MiniProgram 12 | 13 | const TIMEZONE = "asia/shanghai" 14 | const DATETIME_FORMAT = "20060102" 15 | 16 | func NewMiniMiniProgramService(conf *config.Configuration) (*miniProgram.MiniProgram, error) { 17 | var cache kernel.CacheInterface 18 | if conf.MiniProgram.RedisAddr != "" { 19 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 20 | Addrs: []string{conf.MiniProgram.RedisAddr}, 21 | //Addrs: []string{ 22 | // "47.108.182.200:7000", 23 | // "47.108.182.200:7001", 24 | // "47.108.182.200:7002", 25 | //}, 26 | //Username: "michaelhu", 27 | //Password: "111111", 28 | }) 29 | } 30 | app, err := miniProgram.NewMiniProgram(&miniProgram.UserConfig{ 31 | AppID: conf.MiniProgram.AppID, // 小程序、公众号或者企业微信的appid 32 | Secret: conf.MiniProgram.Secret, // 商户号 appID 33 | ResponseType: response.TYPE_MAP, 34 | Token: conf.MiniProgram.MessageToken, 35 | AESKey: conf.MiniProgram.MessageAesKey, 36 | 37 | AppKey: conf.MiniProgram.VirtualPayAppKey, 38 | OfferID: conf.MiniProgram.VirtualPayOfferID, 39 | Http: miniProgram.Http{}, 40 | Log: miniProgram.Log{ 41 | Driver: &drivers.SimpleLogger{}, 42 | Level: "debug", 43 | File: "./wechat.log", 44 | }, 45 | //"sandbox": true, 46 | Cache: cache, 47 | HttpDebug: true, 48 | Debug: false, 49 | }) 50 | 51 | return app, err 52 | } 53 | -------------------------------------------------------------------------------- /controllers/payment/transfer/batch.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/transfer/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | func APIBatchTransfer(c *gin.Context) { 11 | 12 | appId := services.PaymentApp.GetConfig().GetString("app_id", "") 13 | 14 | transfer := &request.RequestTransferBatch{ 15 | AppID: appId, 16 | OutBatchNO: "0010010404201411170000046522", 17 | BatchName: "batch-1", 18 | BatchRemark: "batch-1-remark", 19 | TotalAmount: 30, 20 | TotalNum: 1, 21 | TransferDetailList: []*request.TransferDetail{ 22 | &request.TransferDetail{ 23 | OutDetailNO: "00100104042014111700000465221", 24 | TransferAmount: 30, 25 | TransferRemark: "remark", 26 | OpenID: "o4QEk5Kc_y8QTrENCpKoxYhS4jkg", 27 | //UserName: object.NewNullString("username", true), 28 | }, 29 | }, 30 | } 31 | 32 | payResult, err := services.PaymentApp.TransferBatch.Batch(c.Request.Context(), transfer) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, payResult) 38 | } 39 | 40 | func APIQueryBatchOrder(c *gin.Context) { 41 | 42 | rs, err := services.PaymentApp.TransferBatch.QueryBatch( 43 | c.Request.Context(), 44 | "{batchID}}", 45 | true, 46 | 0, 47 | 10, 48 | "") 49 | if err != nil { 50 | panic(nil) 51 | } 52 | 53 | c.JSON(http.StatusOK, rs) 54 | } 55 | 56 | func APIQueryBatchOrderDetail(c *gin.Context) { 57 | 58 | rs, err := services.PaymentApp.TransferBatch.QueryBatchDetail( 59 | c.Request.Context(), 60 | "{batchID}}", 61 | "{batchDetailID}}") 62 | 63 | if err != nil { 64 | panic(nil) 65 | } 66 | 67 | c.JSON(http.StatusOK, rs) 68 | } 69 | -------------------------------------------------------------------------------- /controllers/official-account/jssdk.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerLibs/v3/object" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/response" 7 | "github.com/gin-gonic/gin" 8 | "net/http" 9 | "power-wechat-tutorial/services" 10 | ) 11 | 12 | func APITicketGet(c *gin.Context) { 13 | 14 | services.OfficialAccountApp.AccessToken.SetCacheKey("456") 15 | services.OfficialAccountApp.AccessToken.SetCustomToken = func(token *response.ResponseGetToken) interface{} { 16 | fmt.Dump("SetCustomToken", token) 17 | return nil 18 | //return "72_iraMZORXFIW6IM7bLyP3e3MMcEDkkrRXywvPAy3D7KI5lpSbMWh5ZgQUUSfl7tXg2Uq-aU_C3Vkj551IUTBPD58WFbgTdEt-csoGPQ8Hkf88DpUVs0MKtrDVhGwXNQiAFACSV" 19 | } 20 | services.OfficialAccountApp.AccessToken.GetCustomToken = func(key string, refresh bool) object.HashMap { 21 | fmt.Dump("GetCustomToken", key, refresh) 22 | return object.HashMap{ 23 | "access_token": "72_ggzUdSgH99StJ2EhmuaIbHHUP9_3rDvdnQVQ9eoX5gwmNfuLpJgBUb5uPgdoh4aoVv9jYz3EKglRT73ppWqgRwzirNQM-bHaToDQ83ux1sFdCr5GK7jxYQfAESoCOEaAHAKWM", 24 | "expires_in": float64(7200), 25 | } 26 | } 27 | 28 | res, err := services.OfficialAccountApp.JSSDK.GetTicket(c.Request.Context(), false, "jsapi") 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | c.JSON(http.StatusOK, res) 35 | } 36 | 37 | func JSSDKBuildConfig(ctx *gin.Context) { 38 | url := "https://www.artisan-cloud.com/" 39 | jsapiList := []string{"chooseImage"} 40 | debug := false 41 | beta := false 42 | openTagList := []string{"wx-open-launch-app", "wx-open-launch-weapp"} 43 | data, err := services.OfficialAccountApp.JSSDK.BuildConfig(ctx.Request.Context(), jsapiList, debug, beta, openTagList, url) 44 | if err != nil { 45 | ctx.String(http.StatusBadRequest, err.Error()) 46 | return 47 | } 48 | ctx.JSON(http.StatusOK, data) 49 | } 50 | -------------------------------------------------------------------------------- /controllers/official-account/uniform-message.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/uniformMessage/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 下发小程序和公众号统一的服务消息 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html 13 | func UniformMessageSend(c *gin.Context) { 14 | 15 | openID, exist := c.GetQuery("openID") 16 | if !exist { 17 | panic("parameter open id expected") 18 | } 19 | templateID := c.DefaultQuery("templateID", "SPNl7d_nnSnssgyj7ZJ6v9B-N04ZBspts_PYvdF3D-8") 20 | 21 | rs, err := services.MiniProgramApp.UniformMessage.Send(c.Request.Context(), &request.RequestUniformMessageSend{ 22 | ToUser: openID, 23 | MpTemplateMsg: &request.MPTemplateMsg{ 24 | AppID: services.OfficialAccountApp.GetConfig().GetString("app_id", ""), 25 | TemplateID: templateID, 26 | Url: "https://powerwechat.artisan-cloud.com/", 27 | //MiniProgram: &request.MPTemplateMsgMiniProgram{ 28 | // AppID: "", 29 | // PagePath: "", 30 | //}, 31 | Data: &power.HashMap{ 32 | "first": &power.HashMap{ 33 | "value": "恭喜你购买成功!", 34 | "color": "#173177", 35 | }, 36 | "DateTime": &power.HashMap{ 37 | "value": "3-5 16:22", 38 | "color": "#173177", 39 | }, 40 | "PayAmount": &power.HashMap{ 41 | "value": "39.8元", 42 | "color": "#173177", 43 | }, 44 | "Location": &power.HashMap{ 45 | "value": "上海市长宁区", 46 | "color": "#173177", 47 | }, 48 | "remark": &power.HashMap{ 49 | "value": "欢迎再次购买!", 50 | "color": "#173177", 51 | }, 52 | }, 53 | }, 54 | }) 55 | 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | c.JSON(http.StatusOK, rs) 61 | } 62 | -------------------------------------------------------------------------------- /controllers/wecom/external-contact/group-chat.go: -------------------------------------------------------------------------------- 1 | package external_contact 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/externalContact/groupChat/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | "strconv" 10 | ) 11 | 12 | // 获取客户群列表 13 | // https://work.weixin.qq.com/api/doc/90000/90135/92120 14 | func APIExternalContactGroupChatList(c *gin.Context) { 15 | 16 | options := &request.RequestGroupChatList{ 17 | StatusFilter: 0, 18 | OwnerFilter: &power.HashMap{ 19 | "userid_list": []string{"abel"}, 20 | }, 21 | Cursor: c.DefaultQuery("cursor", "r9FqSqsI8fgNbHLHE5QoCP50UIg2cFQbfma3l2QsmwI"), 22 | Limit: 10, 23 | } 24 | res, err := services.WeComContactApp.ExternalContactGroupChat.List(c.Request.Context(), options) 25 | 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | c.JSON(http.StatusOK, res) 31 | } 32 | 33 | // 获取客户群详情 34 | // https://work.weixin.qq.com/api/doc/90000/90135/92122 35 | func APIExternalContactGroupChatGet(c *gin.Context) { 36 | 37 | chatID := c.DefaultQuery("chatID", "wrOgQhDgAAMYQiS5ol9G7gK9JVAAAA") 38 | needName := c.DefaultQuery("needName", "true") 39 | bNeedName, _ := strconv.Atoi(needName) 40 | 41 | res, err := services.WeComContactApp.ExternalContactGroupChat.Get(c.Request.Context(), chatID, bNeedName) 42 | 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | c.JSON(http.StatusOK, res) 48 | } 49 | 50 | // 客户群opengid转换 51 | // https://work.weixin.qq.com/api/doc/90000/90135/94822 52 | func APIExternalContactOpenGIDToChatID(c *gin.Context) { 53 | openID := c.DefaultQuery("openID", "oAAAAAAA") 54 | 55 | res, err := services.WeComContactApp.ExternalContactGroupChat.OpenGIDToChatID(c.Request.Context(), openID) 56 | 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | c.JSON(http.StatusOK, res) 62 | } 63 | -------------------------------------------------------------------------------- /controllers/miniprogram/server.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerLibs/v3/http/helper" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract" 8 | models2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models" 9 | "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/server/handlers/models" 10 | "github.com/gin-gonic/gin" 11 | "power-wechat-tutorial/services" 12 | ) 13 | 14 | // 回调配置 15 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 16 | func CallbackVerify(c *gin.Context) { 17 | rs, err := services.MiniProgramApp.Server.VerifyURL(c.Request) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | // 选择1 23 | //text, _ := ioutil.ReadAll(rs.Body) 24 | //c.String(http.StatusOK, string(text)) 25 | 26 | // 选择2 27 | err = helper.HttpResponseSend(rs, c.Writer) 28 | 29 | } 30 | 31 | // 回调配置 32 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 33 | func CallbackNotify(c *gin.Context) { 34 | //requestXML, _ := io.ReadAll(c.Request.Body) 35 | //c.Request.Body = io.NopCloser(bytes.NewBuffer(requestXML)) 36 | //println(string(requestXML)) 37 | 38 | rs, err := services.MiniProgramApp.Server.Notify(c.Request, func(event contract.EventInterface) interface{} { 39 | fmt.Dump("event", event) 40 | //return "handle callback" 41 | 42 | switch event.GetMsgType() { 43 | case models2.CALLBACK_MSG_TYPE_TEXT: 44 | msg := models.MessageText{} 45 | err := event.ReadMessage(&msg) 46 | if err != nil { 47 | println(err.Error()) 48 | return "error" 49 | } 50 | fmt.Dump(msg) 51 | } 52 | 53 | return kernel.SUCCESS_EMPTY_RESPONSE 54 | 55 | }) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | err = helper.HttpResponseSend(rs, c.Writer) 61 | 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /routes/open-platform.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "power-wechat-tutorial/controllers/open-platform" 6 | ) 7 | 8 | func InitOpenPlatformAPIRoutes(r *gin.Engine) { 9 | 10 | apiRouter := r.Group("/wx/api/openplatform") 11 | { 12 | 13 | // auth callback 14 | apiRouter.GET("/callback", open_platform.HandleAuthorize) 15 | apiRouter.POST("/callback", open_platform.APIOpenPlatformCallback) 16 | apiRouter.POST("/callback/:appID", open_platform.APIOpenPlatformCallbackWithApp) 17 | 18 | // auth 19 | apiRouter.GET("/createPreAuthCode", open_platform.APIOpenPlatformPreAuthCode) 20 | apiRouter.GET("/getFastRegistrationURL", open_platform.GetFastRegistrationURL) 21 | apiRouter.GET("/auth", open_platform.HandleAuthorize) 22 | 23 | // authorizer info 24 | apiRouter.GET("/getAuthorizer", open_platform.GetAuthorizer) 25 | apiRouter.GET("/getAuthorizerList", open_platform.GetAuthorizers) 26 | apiRouter.GET("/getAuthorizerOption", open_platform.GetAuthorizerOption) 27 | apiRouter.GET("/setAuthorizerOption", open_platform.SetAuthorizerOption) 28 | 29 | // delegate 30 | apiRouter.GET("/getAuthorizerOfficialAccount", open_platform.GetAuthorizerOfficialAccount) 31 | apiRouter.GET("/getAuthorizerOfficialAccountUser", open_platform.GetAuthorizerOfficialAccountUser) 32 | apiRouter.GET("/getAuthorizerMiniProgram", open_platform.GetAuthorizerMiniProgram) 33 | apiRouter.GET("/authorizerAccountCreate", open_platform.AuthorizerAccountCreate) 34 | apiRouter.GET("/authorizerAccountBind", open_platform.AuthorizerAccountBind) 35 | apiRouter.GET("/authorizerAccountUnbind", open_platform.AuthorizerAccountUnbind) 36 | apiRouter.GET("/authorizerAccountGet", open_platform.AuthorizerAccountGet) 37 | apiRouter.GET("/officialAccountDemo", open_platform.OfficialAccountDemo) 38 | apiRouter.GET("/miniProgramDemo", open_platform.MiniProgramDemo) 39 | 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /controllers/wecom/msg-audit.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 获取会话内容存档开启成员列表 11 | // https://open.work.weixin.qq.com/api/doc/90000/90135/91614 12 | func APIMsgAuditGetPermitUserList(c *gin.Context) { 13 | 14 | msgType := "1" 15 | res, err := services.WeComApp.MsgAudit.GetPermitUsersList(c.Request.Context(), msgType) 16 | 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | c.JSON(http.StatusOK, res) 22 | } 23 | 24 | // 获取会话同意情况 25 | // https://open.work.weixin.qq.com/api/doc/90000/90135/91782 26 | func APIMsgAuditCheckSingleAgree(c *gin.Context) { 27 | info := []*power.StringMap{ 28 | {"userid": "XuJinSheng", "exteranalopenid": "wmeDKaCQAAGd9oGiQWxVsAKwV2HxNAAA"}, 29 | {"userid": "XuJinSheng", "exteranalopenid": "wmeDKaCQAAIQ_p7ACn_jpLVBJSGocAAA"}, 30 | {"userid": "XuJinSheng", "exteranalopenid": "wmeDKaCQAAPE_p7ABnxkpLBBJSGocAAA"}, 31 | } 32 | res, err := services.WeComApp.MsgAudit.CheckSingleAgree(c.Request.Context(), info) 33 | 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | c.JSON(http.StatusOK, res) 39 | } 40 | 41 | // 获取会话同意情况 42 | // https://open.work.weixin.qq.com/api/doc/90000/90135/91782 43 | func APIMsgAuditCheckRoomAgree(c *gin.Context) { 44 | 45 | roomID := c.DefaultQuery("roomID", "wrjc7bDwAASxc8tZvBErFE02BtPWyAAA") 46 | 47 | res, err := services.WeComApp.MsgAudit.CheckRoomAgree(c.Request.Context(), roomID) 48 | 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | c.JSON(http.StatusOK, res) 54 | } 55 | 56 | func APIMsgAuditGroupChatGet(c *gin.Context) { 57 | 58 | roomID := c.DefaultQuery("roomID", "wrjc7bDwAASxc8tZvBErFE02BtPWyAAA") 59 | res, err := services.WeComApp.MsgAudit.GroupChatGet(c.Request.Context(), roomID) 60 | 61 | if err != nil { 62 | panic(err) 63 | } 64 | 65 | c.JSON(http.StatusOK, res) 66 | } 67 | -------------------------------------------------------------------------------- /services/payment-service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/response" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment" 7 | "power-wechat-tutorial/config" 8 | ) 9 | 10 | const TRANSACTION_SUCCESS = "TRANSACTION.SUCCESS" 11 | const TRANSACTION_FAILED = "TRANSACTION.FAILED" 12 | 13 | var PaymentApp *payment.Payment 14 | 15 | func NewWXPaymentApp(conf *config.Configuration) (*payment.Payment, error) { 16 | 17 | var cache kernel.CacheInterface 18 | if conf.MiniProgram.RedisAddr != "" { 19 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 20 | Addrs: []string{conf.MiniProgram.RedisAddr}, 21 | }) 22 | } 23 | 24 | Payment, err := payment.NewPayment(&payment.UserConfig{ 25 | AppID: conf.Payment.AppID, // 小程序、公众号或者企业微信的appid 26 | MchID: conf.Payment.MchID, // 商户号 appID 27 | MchApiV3Key: conf.Payment.MchApiV3Key, // 28 | Key: conf.Payment.Key, 29 | CertPath: conf.Payment.CertPath, 30 | KeyPath: conf.Payment.KeyPath, 31 | SerialNo: conf.Payment.SerialNo, 32 | CertificateKeyPath: conf.Payment.CertificateKeyPath, 33 | WechatPaySerial: conf.Payment.WechatPaySerial, 34 | RSAPublicKeyPath: conf.Payment.RSAPublicKeyPath, 35 | NotifyURL: conf.Payment.NotifyURL, 36 | SubMchID: conf.Payment.SubMchID, 37 | SubAppID: conf.Payment.SubAppID, 38 | ResponseType: response.TYPE_MAP, 39 | Cache: cache, 40 | Log: payment.Log{ 41 | Level: "debug", 42 | File: "./wechat.log", 43 | }, 44 | Http: payment.Http{ 45 | Timeout: 30.0, 46 | //BaseURI: "http://127.0.0.1:8888", 47 | BaseURI: "https://api.mch.weixin.qq.com", 48 | }, 49 | 50 | HttpDebug: false, 51 | Debug: false, 52 | //Debug: true, 53 | }) 54 | 55 | return Payment, err 56 | } 57 | -------------------------------------------------------------------------------- /controllers/wecom/oa/journal.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 批量获取汇报记录单号 11 | // https://work.weixin.qq.com/api/doc/90000/90135/93393 12 | func APIJournalGetRecordList(c *gin.Context) { 13 | 14 | starttime := 1606230000 15 | endtime := 1606361304 16 | cursor := 0 17 | limit := 10 18 | filters := []*power.StringMap{ 19 | &power.StringMap{ 20 | "key": "creator", 21 | "value": "kele", 22 | }, 23 | &power.StringMap{ 24 | "key": "department", 25 | "value": "1", 26 | }, 27 | &power.StringMap{ 28 | "key": "template_id", 29 | "value": "3TmALk1ogfgKiQE3e3jRwnTUhMTh8vca1N8zUVNUx", 30 | }, 31 | } 32 | 33 | res, err := services.WeComApp.OAJournal.GetRecordList(c.Request.Context(), starttime, endtime, cursor, limit, filters) 34 | 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | c.JSON(http.StatusOK, res) 40 | } 41 | 42 | // 获取汇报记录详情 43 | // https://work.weixin.qq.com/api/doc/90000/90135/93394 44 | func APIJournalGetRecordDetail(c *gin.Context) { 45 | 46 | journalUUID := c.DefaultQuery("journalUUID", "41eJejN57EJNzr8HrZfmKyCN7xwKw1qRxCZUxCVuo9fsWVMSKac6nk4q8rARTDaVNdx") 47 | res, err := services.WeComApp.OAJournal.GetRecordDetail(c.Request.Context(), journalUUID) 48 | 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | c.JSON(http.StatusOK, res) 54 | } 55 | 56 | // 获取汇报记录详情 57 | // https://work.weixin.qq.com/api/doc/90000/90135/93395 58 | func APIJournalGetStatList(c *gin.Context) { 59 | 60 | templateID := c.DefaultQuery("templateID", "41eJejN57EJNzr8HrZfmKyCN7xwKw1qRxCZUxCVuo9fsWVMSKac6nk4q8rARTDaVNdx") 61 | startTime := 1604160000 62 | endTime := 1606363092 63 | res, err := services.WeComApp.OAJournal.GetStatList(c.Request.Context(), templateID, startTime, endTime) 64 | 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | c.JSON(http.StatusOK, res) 70 | } 71 | -------------------------------------------------------------------------------- /controllers/miniprogram/search.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 本接口提供基于小程序的站内搜商品图片搜索能力 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.imageSearch.html 12 | func APISearchImageSearch(c *gin.Context) { 13 | 14 | options := []*power.HashMap{ 15 | &power.HashMap{ 16 | "title": "image title1", 17 | "img_url": "", 18 | "price": "123", 19 | "path": "path", 20 | }, 21 | &power.HashMap{ 22 | "title": "image title2", 23 | "img_url": "", 24 | "price": "123", 25 | "path": "path", 26 | }, 27 | } 28 | 29 | rs, err := services.MiniProgramApp.Search.ImageSearch(c.Request.Context(), options) 30 | 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | c.JSON(http.StatusOK, rs) 36 | } 37 | 38 | // 小程序内部搜索API提供针对页面的查询能力 39 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.siteSearch.html 40 | func APISearchSiteSearch(c *gin.Context) { 41 | 42 | rs, err := services.MiniProgramApp.Search.SiteSearch(c.Request.Context(), "test", "pages/index/index") 43 | 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | c.JSON(http.StatusOK, rs) 49 | 50 | } 51 | 52 | // 小程序开发者可以通过本接口提交小程序页面url及参数信息(不要推送webview页面) 53 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.submitPages.html 54 | func APISearchSubmitPages(c *gin.Context) { 55 | options := []*power.HashMap{ 56 | &power.HashMap{ 57 | "path": "pages/index/index", 58 | "query": "userName=wechat_user", 59 | }, 60 | &power.HashMap{ 61 | "path": "pages/video/index", 62 | "query": "vid=123", 63 | }, 64 | } 65 | 66 | rs, err := services.MiniProgramApp.Search.SubmitPages(c.Request.Context(), options) 67 | 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | c.JSON(http.StatusOK, rs) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /controllers/miniprogram/wxacode.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "io" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 获取小程序二维码,适用于需要的码数量较少的业务场景 11 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.createQRCode.html 12 | func APIWXACodeCreateQRCode(c *gin.Context) { 13 | 14 | path, exist := c.GetQuery("path") 15 | if !exist { 16 | panic("parameter path expected") 17 | } 18 | 19 | rs, err := services.MiniProgramApp.WXACode.CreateQRCode(c.Request.Context(), path, 430) 20 | 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | io.Copy(c.Writer, rs.Body) 26 | } 27 | 28 | // 获取小程序码,适用于需要的码数量较少的业务场景 29 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.get.html 30 | func APIWXACodeGet(c *gin.Context) { 31 | 32 | path, exist := c.GetQuery("path") 33 | if !exist { 34 | panic("parameter path expected") 35 | } 36 | 37 | rs, err := services.MiniProgramApp.WXACode.Get( 38 | c.Request.Context(), 39 | path, 40 | 430, 41 | false, 42 | &power.HashMap{ 43 | "r": 0, 44 | "g": 0, 45 | "b": 0, 46 | }, 47 | false, 48 | "develop", 49 | ) 50 | 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | io.Copy(c.Writer, rs.Body) 56 | } 57 | 58 | // 获取小程序码,适用于需要的码数量极多的业务场景 59 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/qr-code/wxacode.getUnlimited.html 60 | func APIWXACodeGetUnlimited(c *gin.Context) { 61 | 62 | page, exist := c.GetQuery("page") 63 | if !exist { 64 | panic("parameter page expected") 65 | } 66 | 67 | rs, err := services.MiniProgramApp.WXACode.GetUnlimited( 68 | c.Request.Context(), 69 | "a=1&b=123", 70 | page, 71 | false, 72 | "develop", 73 | 430, 74 | false, 75 | &power.HashMap{ 76 | "r": 0, 77 | "g": 0, 78 | "b": 0, 79 | }, 80 | false, 81 | ) 82 | 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | io.Copy(c.Writer, rs.Body) 88 | } 89 | -------------------------------------------------------------------------------- /controllers/payment/profitSharing.go: -------------------------------------------------------------------------------- 1 | package payment 2 | 3 | import ( 4 | "encoding/base64" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/profitSharing/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | func APIOrders(c *gin.Context) { 12 | 13 | transactionID := c.DefaultQuery("transactionID", "") 14 | outOrderNo := c.DefaultQuery("OutRefundNo", "") 15 | 16 | config := services.MiniProgramApp.GetConfig() 17 | 18 | cipherdata, err := services.PaymentApp.Base.BaseClient.RsaOAEP.RSAEncryptor.Encrypt([]byte("hu89ohu89ohu89o")) 19 | bufferEncryptedName := base64.StdEncoding.EncodeToString(cipherdata) 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | para := &request.RequestShare{ 25 | AppID: config.GetString("app_id", ""), 26 | TransactionID: transactionID, 27 | OutOrderNO: outOrderNo, 28 | Receivers: []*request.Receiver{ 29 | &request.Receiver{ 30 | Type: "MERCHANT_ID", 31 | Account: "86693852", 32 | Name: string(bufferEncryptedName), 33 | Amount: 300, 34 | Description: "分给商户A", 35 | }, 36 | }, 37 | //UnfreezeUnSplit: true, 38 | UnfreezeUnSplit: false, 39 | } 40 | 41 | rs, err := services.PaymentApp.ProfitSharing.Share(c.Request.Context(), para) 42 | if err != nil { 43 | panic(err) 44 | } 45 | c.JSON(http.StatusOK, rs) 46 | 47 | } 48 | 49 | func APIAddReceiver(c *gin.Context) { 50 | 51 | cipherdata, err := services.PaymentApp.Base.BaseClient.RsaOAEP.RSAEncryptor.Encrypt([]byte("hu89ohu89ohu89o")) 52 | bufferEncryptedName := base64.StdEncoding.EncodeToString(cipherdata) 53 | 54 | receiverType := "MERCHANT_ID" 55 | account := "86693852" 56 | name := bufferEncryptedName 57 | relationType := "STAFF" 58 | customRelation := "分给商户A" 59 | 60 | rs, err := services.PaymentApp.ProfitSharing.AddReceiver(c.Request.Context(), 61 | receiverType, account, name, 62 | relationType, customRelation) 63 | if err != nil { 64 | panic(err) 65 | } 66 | c.JSON(http.StatusOK, rs) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /controllers/payment/transfer/transfer.go: -------------------------------------------------------------------------------- 1 | package transfer 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/transfer/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | func APIToTransfer(c *gin.Context) { 11 | 12 | mchId := services.PaymentApp.GetConfig().GetString("mch_id", "") 13 | appId := services.PaymentApp.GetConfig().GetString("app_id", "") 14 | 15 | options := &request.RequestTransferToBalance{ 16 | MchAppID: appId, 17 | MchID: mchId, 18 | DeviceInfo: "", 19 | NonceStr: "", 20 | PartnerTradeNo: "0010010404201411170000046545", 21 | OpenID: "o4QEk5Kc_y8QTrENCpKoxYhS4jkg", 22 | CheckName: "NO_CHECK", 23 | ReUserName: "", 24 | Amount: 30, 25 | Desc: "活动奖励", 26 | SpBillCreateIP: "", 27 | Scene: "", 28 | BrandID: 0, 29 | FinderTemplateID: "", 30 | } 31 | 32 | payConf, err := services.PaymentApp.Transfer.ToBalance(c.Request.Context(), options) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.XML(http.StatusOK, payConf) 38 | } 39 | 40 | func APIQueryBalanceOrder(c *gin.Context) { 41 | 42 | rs, err := services.PaymentApp.Transfer.QueryBalanceOrder(c.Request.Context(), "0010010404201411170000046545") 43 | if err != nil { 44 | panic(nil) 45 | } 46 | 47 | c.JSON(http.StatusOK, rs) 48 | } 49 | 50 | func APIToBankCard(c *gin.Context) { 51 | 52 | mchId := services.PaymentApp.GetConfig().GetString("mch_id", "") 53 | 54 | options := &request.RequestToBankCard{ 55 | MchID: mchId, 56 | BankCode: "0010010404201411170000046545", 57 | Amount: 30, 58 | Desc: "活动奖励", 59 | EncBankNO: "123213", 60 | EncTrueName: "321312", 61 | NonceStr: "1231232131", 62 | PartnerTradeNO: "213313213212", 63 | } 64 | 65 | payConf, err := services.PaymentApp.Transfer.ToBankCard(c.Request.Context(), options) 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | c.XML(http.StatusOK, payConf) 71 | } 72 | -------------------------------------------------------------------------------- /controllers/wecom/user/linked-corp.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // APILinkedCorpAgentGetPermList 获取应用的可见范围 10 | // https://open.work.weixin.qq.com/api/doc/90000/90135/93172 11 | func APILinkedCorpAgentGetPermList(c *gin.Context) { 12 | 13 | res, err := services.WeComApp.UserLinkedCorp.GetPermList(c.Request.Context()) 14 | if err != nil { 15 | panic(err) 16 | } 17 | c.JSON(http.StatusOK, res) 18 | } 19 | 20 | // APILinkedCorpUserGet 获取互联企业成员详细信息 21 | // https://work.weixin.qq.com/api/doc/90000/90135/93171 22 | func APILinkedCorpUserGet(c *gin.Context) { 23 | userID := c.DefaultQuery("userID", "walle") 24 | res, err := services.WeComApp.UserLinkedCorp.GetUser(c.Request.Context(), userID) 25 | if err != nil { 26 | panic(err) 27 | } 28 | c.JSON(http.StatusOK, res) 29 | } 30 | 31 | // APILinkedCorpUserSimpleList 获取互联企业部门成员 32 | // https://work.weixin.qq.com/api/doc/90000/90135/93168 33 | func APILinkedCorpUserSimpleList(c *gin.Context) { 34 | departmentID := c.DefaultQuery("departmentID", "xxx") 35 | res, err := services.WeComApp.UserLinkedCorp.GetUserSimpleList(c.Request.Context(), departmentID, true) 36 | if err != nil { 37 | panic(err) 38 | } 39 | c.JSON(http.StatusOK, res) 40 | 41 | } 42 | 43 | // APILinkedCorpUserList 获取互联企业部门成员详情 44 | // https://work.weixin.qq.com/api/doc/90000/90135/93169 45 | func APILinkedCorpUserList(c *gin.Context) { 46 | departmentID := c.DefaultQuery("departmentID", "xxx") 47 | res, err := services.WeComApp.UserLinkedCorp.GetUserList(c.Request.Context(), departmentID, true) 48 | if err != nil { 49 | panic(err) 50 | } 51 | c.JSON(http.StatusOK, res) 52 | } 53 | 54 | // APILinkedCorpDepartmentList 获取互联企业部门列表 55 | // https://work.weixin.qq.com/api/doc/90000/90135/93170 56 | func APILinkedCorpDepartmentList(c *gin.Context) { 57 | departmentID := c.DefaultQuery("departmentID", "xxx") 58 | res, err := services.WeComApp.UserLinkedCorp.GetDepartmentList(c.Request.Context(), departmentID) 59 | if err != nil { 60 | panic(err) 61 | } 62 | c.JSON(http.StatusOK, res) 63 | } 64 | -------------------------------------------------------------------------------- /controllers/wecom/user/user-callback.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerLibs/v3/http/helper" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract" 8 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/server/handlers/models" 9 | "github.com/gin-gonic/gin" 10 | "io/ioutil" 11 | "net/http" 12 | "power-wechat-tutorial/services" 13 | ) 14 | 15 | // 回调配置 16 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 17 | func CallbackVerify(c *gin.Context) { 18 | rs, err := services.WeComContactApp.Server.Serve(c.Request) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | text, _ := ioutil.ReadAll(rs.Body) 24 | c.String(http.StatusOK, string(text)) 25 | 26 | } 27 | 28 | // 回调配置 29 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 30 | func CallbackNotify(c *gin.Context) { 31 | 32 | rs, err := services.WeComContactApp.Server.Notify(c.Request, func(event contract.EventInterface) interface{} { 33 | fmt.Dump("event", event) 34 | 35 | // 这里需要获取到事件类型,然后把对应的结构体传递进去进一步解析 36 | // 所有包含的结构体请参考: https://github.com/ArtisanCloud/PowerWeChat/tree/master/src/work/server/handlers/models 37 | if event.GetEvent() == models.CALLBACK_EVENT_CHANGE_CONTACT && event.GetChangeType() == models.CALLBACK_EVENT_CHANGE_TYPE_CREATE_PARTY { 38 | //msg := models.EventPartyCreate{} 39 | msg := models.EventKFMsgOrEvent{} 40 | err := event.ReadMessage(&msg) 41 | if err != nil { 42 | println(err.Error()) 43 | return "error" 44 | } 45 | fmt.Dump(msg) 46 | } 47 | 48 | // 假设员工给应用发送消息,这里可以直接回复消息文本, 49 | // return "I'm recv..." 50 | 51 | // 这里回复success告诉微信我收到了,后续需要回复用户信息可以主动调发消息接口 52 | return kernel.SUCCESS_EMPTY_RESPONSE 53 | }) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | // 选择1: 直接把gin context writer传入,会自动回复。 59 | err = helper.HttpResponseSend(rs, c.Writer) 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | // 选择2: 或者是把内容读取出来,回复 65 | //text, _ := ioutil.ReadAll(rs.Body) 66 | //c.String(http.StatusOK, string(text)) 67 | 68 | } 69 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "log" 6 | "power-wechat-tutorial/config" 7 | "power-wechat-tutorial/routes" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | var Host = "" 12 | var Port = "8888" 13 | 14 | // @title PowerWechat API Docs 15 | // @version 1.0.1 16 | // @description 这是一个开源的使用教程,包含PowerWechat的大部分接口调试代码. 17 | // @termsOfService http://artisan-cloud.com/terms/ 18 | 19 | // @contact.name ArtisanCloud Support 20 | // @contact.url https://powerwechat.artisan-cloud.com/zh/start/qa.html 21 | // @contact.email matrix-x@artisan-cloud.como 22 | 23 | // @license.name MIT 2.0 24 | // @license.url https://github.com/ArtisanCloud/PowerWeChat?tab=MIT-1-ov-file#readme 25 | 26 | // @host localhost:8080 27 | // @BasePath /api/v1 28 | 29 | // @securityDefinitions.basic BasicAuth 30 | 31 | // @externalDocs.description OpenAPI 32 | // @externalDocs.url https://swagger.io/resources/open-api/ 33 | func main() { 34 | conf := config.Get() 35 | 36 | var err error 37 | services.PaymentApp, err = services.NewWXPaymentApp(conf) 38 | if err != nil || services.PaymentApp == nil { 39 | panic(err) 40 | } 41 | 42 | services.MiniProgramApp, err = services.NewMiniMiniProgramService(conf) 43 | if err != nil || services.MiniProgramApp == nil { 44 | panic(err) 45 | } 46 | 47 | services.OfficialAccountApp, err = services.NewOfficialAccountAppService(conf) 48 | if err != nil || services.OfficialAccountApp == nil { 49 | panic(err) 50 | } 51 | 52 | services.WeComApp, err = services.NewWeComService(conf) 53 | if err != nil || services.WeComApp == nil { 54 | panic(err) 55 | } 56 | 57 | services.WeComContactApp, err = services.NewWeComContactService(conf) 58 | if err != nil || services.WeComContactApp == nil { 59 | panic(err) 60 | } 61 | 62 | services.OpenPlatformApp, err = services.NewOpenPlatformAppService(conf) 63 | if err != nil || services.OpenPlatformApp == nil { 64 | panic(err) 65 | } 66 | 67 | r := gin.Default() 68 | 69 | // Initialize the routes 70 | routes.InitializeRoutes(r) 71 | 72 | log.Fatalln(r.Run(Host + ":" + Port)) 73 | 74 | } 75 | -------------------------------------------------------------------------------- /controllers/wecom/account-service/message.go: -------------------------------------------------------------------------------- 1 | package account_service 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/accountService/message/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 读取消息 11 | // https://work.weixin.qq.com/api/doc/90000/90135/94670 12 | func APIAccountServiceSyncMsg(c *gin.Context) { 13 | 14 | cursor := c.DefaultQuery("cursor", "4gw7MepFLfgF2VC5npN") 15 | token := c.DefaultQuery("token", "ENCApHxnGDNAVNY4AaSJKj4Tb5mwsEMzxhFmHVGcra996NR") 16 | limit := 1000 17 | 18 | res, err := services.WeComApp.AccountServiceMessage.SyncMsg(c.Request.Context(), cursor, token, limit, 0, "") 19 | 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | c.JSON(http.StatusOK, res) 25 | } 26 | 27 | // 发送消息 28 | // https://work.weixin.qq.com/api/doc/90000/90135/90236 29 | func APIAccountServiceSendMsg(c *gin.Context) { 30 | 31 | options := &request.RequestAccountServiceSendMsg{ 32 | ToUser: c.DefaultQuery("toUser", "EXTERNAL_USERID"), 33 | OpenKfid: c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx"), 34 | MsgID: c.DefaultQuery("msgID", "MSGID"), 35 | MsgType: "text", 36 | Text: &request.RequestAccountServiceMsgText{ 37 | Content: "你购买的物品已发货,可点击链接查看物流状态http://work.weixin.qq.com/xxxxxx", 38 | }, 39 | } 40 | 41 | res, err := services.WeComApp.AccountServiceMessage.SendMsg(c.Request.Context(), options) 42 | 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | c.JSON(http.StatusOK, res) 48 | } 49 | 50 | // 发送消息 51 | // https://work.weixin.qq.com/api/doc/90000/90135/95122 52 | func APIAccountServiceSendMsgOnEvent(c *gin.Context) { 53 | 54 | options := &request.RequestAccountServiceSendMsgOnEvent{ 55 | Code: c.DefaultQuery("code", "CODE"), 56 | MsgID: c.DefaultQuery("msgID", "MSG_ID"), 57 | MsgType: "text", // 对应的消息体字段,目前支持文本与菜单消息,详见下文 58 | Text: request.RequestAccountServiceMsgText{ 59 | Content: "欢迎咨询", 60 | }, 61 | } 62 | 63 | res, err := services.WeComApp.AccountServiceMessage.SendMsgOnEvent(c.Request.Context(), options) 64 | 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | c.JSON(http.StatusOK, res) 70 | } 71 | -------------------------------------------------------------------------------- /controllers/wecom/message/message-callback.go: -------------------------------------------------------------------------------- 1 | package message 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 6 | "github.com/ArtisanCloud/PowerLibs/v3/http/helper" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 8 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract" 9 | models2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models" 10 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/server/handlers/models" 11 | "github.com/gin-gonic/gin" 12 | "power-wechat-tutorial/services" 13 | ) 14 | 15 | func TestBuffer(c *gin.Context) { 16 | 17 | textXML := "163440105270196990678405619241000008" 18 | var md interface{} 19 | md2 := models.MessageText{} 20 | err := xml.Unmarshal([]byte(textXML), &md2) 21 | md = md2 22 | fmt.Dump(md, err) 23 | } 24 | 25 | // 回调配置 26 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 27 | func CallbackVerify(c *gin.Context) { 28 | rs, err := services.WeComApp.Server.Serve(c.Request) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | // 选择1 34 | //text, _ := ioutil.ReadAll(rs.Body) 35 | //c.String(http.StatusOK, string(text)) 36 | 37 | // 选择2 38 | err = helper.HttpResponseSend(rs, c.Writer) 39 | 40 | } 41 | 42 | // 回调配置 43 | // https://work.weixin.qq.com/api/doc/90000/90135/90930 44 | func CallbackNotify(c *gin.Context) { 45 | 46 | rs, err := services.WeComApp.Server.Notify(c.Request, func(event contract.EventInterface) interface{} { 47 | fmt.Dump("event", event) 48 | //return "handle callback" 49 | 50 | switch event.GetMsgType() { 51 | case models2.CALLBACK_MSG_TYPE_TEXT: 52 | msg := models.MessageText{} 53 | err := event.ReadMessage(&msg) 54 | if err != nil { 55 | println(err.Error()) 56 | return "error" 57 | } 58 | fmt.Dump(msg) 59 | } 60 | 61 | return kernel.SUCCESS_EMPTY_RESPONSE 62 | 63 | }) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | err = helper.HttpResponseSend(rs, c.Writer) 69 | 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerWeChat Tutorial 使用实例 2 | 3 | 我们单独写了一个项目[PowerWechatTutorial](https://github.com/ArtisanCloud/PowerWechatTutorial),基本上覆盖了所有的API使用,希望能够帮助大家更快的上手Golang WeChat开发。 4 | 5 | ### 下载项目 6 | ```bash 7 | git clone https://github.com/ArtisanCloud/PowerWechatTutorial.git 8 | ``` 9 | 10 | ### Insomnia配置(可选) 11 | 可以下载insomnia的配置文件,便于在Tutorial里使用insomnia调试。 12 | [insomnia.json](./insomnia.json) 13 | 14 | ### 项目配置 15 | 在项目根目录下,新建一个`config.yaml`, 把下面字段内容复制进去, 然后执行`go run main.go`。 16 | 如果程序正常启动,访问 [http://localhost:8888](http://localhost:8888) 会返回一个`Hello, PowerWechat` 17 | 18 | ```yaml 19 | # 微信支付配置文档: https://powerwechat.artisan-cloud.com/zh/payment/index.html#userconfig%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E%EF%BC%9A 20 | payment: 21 | appid: xxxxx # 公众号appid、小程序appid、企业微信corpid等 22 | mchid: 100000 # ArtisanCloud商户号 23 | certpath: certs/apiclient_cert.pem # 证书路径 24 | keypath: certs/apiclient_key.pem # 证书私钥路径 25 | serialno: 55D06F99FF64CF1759FDE5B77A0BEC8B67A78C2E 26 | key: xxxxx 27 | mchapiv3key: xxxxx 28 | notifyurl: https://www.artisancloud.cn 29 | redisaddr: localhost:6379 30 | 31 | # 小程序配置文档: https://powerwechat.artisan-cloud.com/zh/mini-program/index.html 32 | miniprogram: 33 | appid: xxxxx 34 | secret: xxxxx 35 | redisaddr: localhost:6379 36 | messagetoken: xxxxx 37 | messageaeskey: xxxxx 38 | 39 | # 企业微信配置文档: https://powerwechat.artisan-cloud.com/zh/wecom/index.html 40 | wecom: 41 | corpid: ww454dfb9d6f6d432a 42 | 43 | # ----- powerwechat-docs-demo start ------ 44 | agent_id: 1000000 45 | secret: xxxxx 46 | messagetoken: xxxxx 47 | messageaeskey: xxxxx 48 | messagecallback: https://www.artisan-cloud.com/message/callback 49 | oauthcallback: https://www.artisan-cloud.com/oauth/callback 50 | # ----- powerwechat-docs-demo end ------ 51 | 52 | # 联系人配置 53 | contactsecret: xxxxx 54 | contacttoken: xxxxx 55 | contactaeskey: xxxxx 56 | contactcallback: https://www.artisan-cloud.com/api/wx/customer 57 | 58 | redisaddr: localhost:6379 59 | 60 | # 公众号配置文档: https://powerwechat.artisan-cloud.com/zh/official-account/index.html 61 | offiaccount: 62 | appid: xxxxx 63 | appsecret: xxxxx 64 | redisaddr: localhost:6379 65 | messagetoken: xxxxx 66 | messageaeskey: xxxxx 67 | ``` 68 | 69 | 在`main.go`里面,你可以选择性的注释掉不需要的模块,避免没有配置的时候报错影响运行。 70 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: /api/v1 2 | externalDocs: 3 | description: OpenAPI 4 | url: https://swagger.io/resources/open-api/ 5 | host: localhost:8080 6 | info: 7 | contact: 8 | email: matrix-x@artisan-cloud.como 9 | name: ArtisanCloud Support 10 | url: https://powerwechat.artisan-cloud.com/zh/start/qa.html 11 | description: 这是一个开源的使用教程,包含PowerWechat的大部分接口调试代码. 12 | license: 13 | name: MIT 2.0 14 | url: https://github.com/ArtisanCloud/PowerWeChat?tab=MIT-1-ov-file#readme 15 | termsOfService: http://artisan-cloud.com/terms/ 16 | title: PowerWechat API Docs 17 | version: 1.0.1 18 | paths: 19 | /clearQuota: 20 | get: 21 | description: "\nSDK产品接口的代码展示:\n```\n返回类型定义如下:\ntype ResponseOfficialAccount 22 | struct {\n\tResponseBase\n\n\tErrCode int `json:\"errcode,omitempty\"`\n\tErrMsg 23 | \ string `json:\"errmsg,omitempty\"`\n\n\tResultCode string `json:\"resultcode,omitempty\"`\n\tResultMsg 24 | \ string `json:\"resultmsg,omitempty\"`\n}\n\n具体使用接口方式:\nfunc ClearQuota(ctx 25 | *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.ClearQuota(ctx.Request.Context())\n\tif 26 | err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, 27 | data)\n}\n```" 28 | responses: {} 29 | summary: 公众号里清空api的调用quota:https://developers.weixin.qq.com/doc/offiaccount/openApi/clear_quota.html 30 | tags: 31 | - OfficialAccount.base.ClearQuota 32 | /getCallbackIp: 33 | get: 34 | description: "\nSDK产品接口的代码展示:\n```\n返回类型定义如下:\ntype ResponseGetCallBackIP struct 35 | {\n\tresponse.ResponseOfficialAccount\n\n\tIPList []string `json:\"ip_list\"`\n}\n\n具体使用接口方式:\nfunc 36 | GetCallbackIP(ctx *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.GetCallbackIP(ctx.Request.Context())\n\tif 37 | err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, 38 | data)\n}\n```" 39 | responses: {} 40 | summary: 获取公众号回调的IP地址:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html 41 | tags: 42 | - OfficialAccount.base.GetCallbackIP 43 | securityDefinitions: 44 | BasicAuth: 45 | type: basic 46 | swagger: "2.0" 47 | -------------------------------------------------------------------------------- /controllers/miniprogram/uniform-message.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/uniformMessage/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 下发小程序和公众号统一的服务消息 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/uniform-message/uniformMessage.send.html 13 | func APIUniformMessageSend(c *gin.Context) { 14 | 15 | openID, exist := c.GetQuery("openID") 16 | if !exist { 17 | panic("parameter open id expected") 18 | } 19 | 20 | rs, err := services.MiniProgramApp.UniformMessage.Send(c.Request.Context(), 21 | &request.RequestUniformMessageSend{ 22 | ToUser: openID, 23 | WeAppTemplateMsg: &request.WeAppTemplateMsg{ 24 | TemplateID: "TEMPLATE_ID", 25 | Page: "page/page/index", 26 | FormID: "FORMID", 27 | Data: &power.HashMap{ 28 | "keyword1": &power.HashMap{ 29 | "value": "339208499", 30 | }, 31 | "keyword2": &power.HashMap{ 32 | "value": "2015年01月05日 12:30", 33 | }, 34 | "keyword3": &power.HashMap{ 35 | "value": "腾讯微信总部", 36 | }, 37 | "keyword4": &power.HashMap{ 38 | "value": "广州市海珠区新港中路397号", 39 | }, 40 | }, 41 | EmphasisKeyword: "keyword1.DATA", 42 | }, 43 | MpTemplateMsg: &request.MPTemplateMsg{ 44 | AppID: services.MiniProgramApp.GetConfig().GetString("app_id", ""), 45 | TemplateID: "MTvUCMmZfl-Dv66C5fVWdf4zPJkYSaRbnrtk6DXIfTQ", 46 | Url: "https://weixin.qq.com/download", 47 | MiniProgram: &request.MPTemplateMsgMiniProgram{ 48 | AppID: "", 49 | PagePath: "", 50 | }, 51 | Data: &power.HashMap{ 52 | "first": &power.HashMap{ 53 | "value": "恭喜你购买成功!", 54 | "color": "#173177", 55 | }, 56 | "keyword1": &power.HashMap{ 57 | "value": "巧克力", 58 | "color": "#173177", 59 | }, 60 | "keyword2": &power.HashMap{ 61 | "value": "39.8元", 62 | "color": "#173177", 63 | }, 64 | "keyword3": &power.HashMap{ 65 | "value": "2014年9月22日", 66 | "color": "#173177", 67 | }, 68 | "remark": &power.HashMap{ 69 | "value": "欢迎再次购买!", 70 | "color": "#173177", 71 | }, 72 | }, 73 | }, 74 | }) 75 | 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | c.JSON(http.StatusOK, rs) 81 | } 82 | -------------------------------------------------------------------------------- /controllers/wecom/invoice.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/invoice/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 查询电子发票 11 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90284 12 | func APIInvoiceReimburseGetInvoiceInfo(c *gin.Context) { 13 | cardID := c.DefaultQuery("cardID", "CARDID") 14 | encryptCode := c.DefaultQuery("encryptCode", "ENCRYPTCODE") 15 | 16 | res, err := services.WeComApp.Invoice.GetInvoiceInfo(c.Request.Context(), cardID, encryptCode) 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, res) 23 | } 24 | 25 | // 更新发票状态 26 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90285 27 | func APIInvoiceReimburseUpdateInvoiceStatus(c *gin.Context) { 28 | cardID := c.DefaultQuery("cardID", "CARDID") 29 | encryptCode := c.DefaultQuery("encryptCode", "ENCRYPTCODE") 30 | reimburseStatus := c.DefaultQuery("status", "INVOICE_REIMBURSE_INIT") 31 | 32 | res, err := services.WeComApp.Invoice.UpdateInvoiceStatus(c.Request.Context(), cardID, encryptCode, reimburseStatus) 33 | 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | c.JSON(http.StatusOK, res) 39 | } 40 | 41 | // 批量更新发票状态 42 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90286 43 | func APIInvoiceReimburseUpdateStatusBatch(c *gin.Context) { 44 | openID := c.DefaultQuery("openID", "OPENID") 45 | reimburseStatus := c.DefaultQuery("status", "INVOICE_REIMBURSE_INIT") 46 | invoiceList := []*request.RequestCardInvoice{ 47 | {CardID: "cardid_1", EncryptCode: "encrypt_code_1"}, 48 | {CardID: "cardid_2", EncryptCode: "encrypt_code_2"}, 49 | } 50 | 51 | res, err := services.WeComApp.Invoice.UpdateStatusBatch(c.Request.Context(), openID, reimburseStatus, invoiceList) 52 | 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | c.JSON(http.StatusOK, res) 58 | } 59 | 60 | // 批量查询电子发票 61 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90287 62 | func APIInvoiceReimburseGetInvoiceInfoBatch(c *gin.Context) { 63 | 64 | invoiceList := []*request.RequestCardInvoice{ 65 | {CardID: "CARDID1", EncryptCode: "ENCRYPTCODE1"}, 66 | {CardID: "CARDID2", EncryptCode: "ENCRYPTCODE2"}, 67 | } 68 | res, err := services.WeComApp.Invoice.GetInvoiceInfoBatch(c.Request.Context(), invoiceList) 69 | 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | c.JSON(http.StatusOK, res) 75 | } 76 | -------------------------------------------------------------------------------- /services/wecom-service.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/logger/drivers" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work" 7 | "power-wechat-tutorial/config" 8 | ) 9 | 10 | var WeComApp *work.Work 11 | var WeComContactApp *work.Work 12 | 13 | func NewWeComService(conf *config.Configuration) (*work.Work, error) { 14 | var cache kernel.CacheInterface 15 | if conf.WeCom.RedisAddr != "" { 16 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 17 | Addrs: []string{conf.MiniProgram.RedisAddr}, 18 | }) 19 | } 20 | 21 | app, err := work.NewWork(&work.UserConfig{ 22 | CorpID: conf.WeCom.CorpID, // 企业微信的corp id,所有企业微信共用一个。 23 | AgentID: conf.WeCom.AgentID, // 内部应用的app id 24 | Secret: conf.WeCom.Secret, // 内部应用的app secret 25 | Token: conf.WeCom.MessageToken, // 内部应用的app token 26 | AESKey: conf.WeCom.MessageAesKey, // 内部应用的app aeskey 27 | CallbackURL: conf.WeCom.MessageCallback, // 内部应用的场景回调设置 28 | OAuth: work.OAuth{ 29 | Callback: conf.WeCom.OAuthCallback, // 内部应用的app oauth url 30 | Scopes: []string{"snsapi_base"}, 31 | }, 32 | Log: work.Log{ 33 | Driver: &drivers.DummyLogger{}, 34 | Level: "debug", 35 | //Level: "off", 36 | File: "./wechat.log", 37 | }, 38 | Cache: cache, 39 | HttpDebug: true, 40 | Debug: false, 41 | }) 42 | 43 | return app, err 44 | } 45 | 46 | func NewWeComContactService(conf *config.Configuration) (*work.Work, error) { 47 | var cache kernel.CacheInterface 48 | if conf.MiniProgram.RedisAddr != "" { 49 | cache = kernel.NewRedisClient(&kernel.UniversalOptions{ 50 | Addrs: []string{conf.MiniProgram.RedisAddr}, 51 | }) 52 | } 53 | 54 | app, err := work.NewWork(&work.UserConfig{ 55 | CorpID: conf.WeCom.CorpID, // 企业微信的app id,所有企业微信共用一个。 56 | AgentID: conf.WeCom.AgentID, // 内部应用的app id 57 | Secret: conf.WeCom.Secret, // 内部应用的app secret 58 | Token: conf.WeCom.ContactToken, // 内部应用的app token 59 | AESKey: conf.WeCom.ContactAESKey, // 内部应用的app aeskey 60 | CallbackURL: conf.WeCom.ContactCallback, // 内部应用的场景回调设置 61 | OAuth: work.OAuth{ 62 | Callback: conf.WeCom.OAuthCallback, // 内部应用的app oauth url 63 | Scopes: nil, 64 | }, 65 | HttpDebug: true, 66 | Debug: false, 67 | Cache: cache, 68 | }) 69 | return app, err 70 | } 71 | -------------------------------------------------------------------------------- /controllers/open-platform/server.go: -------------------------------------------------------------------------------- 1 | package open_platform 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 8 | openplatform "github.com/ArtisanCloud/PowerWeChat/v3/src/openPlatform/server/callbacks" 9 | "github.com/gin-gonic/gin" 10 | "io" 11 | "io/ioutil" 12 | "power-wechat-tutorial/services" 13 | ) 14 | 15 | // 如果要上生产或者一直使用,需要保证开起推送ticket,在这里接受ticket,PowerWechat会一直帮你缓存最新的ticket。 16 | func APIOpenPlatformCallback(context *gin.Context) { 17 | 18 | requestXML, _ := io.ReadAll(context.Request.Body) 19 | context.Request.Body = io.NopCloser(bytes.NewBuffer(requestXML)) 20 | println(string(requestXML)) 21 | 22 | var err error 23 | 24 | rs, err := services.OpenPlatformApp.Server.Notify(context.Request, func(event *openplatform.Callback, decrypted []byte, infoType string) (result interface{}) { 25 | 26 | result = kernel.SUCCESS_EMPTY_RESPONSE 27 | 28 | switch infoType { 29 | case openplatform.EVENT_COMPONENT_VERIFY_TICKET: 30 | msg := &openplatform.EventVerifyTicket{} 31 | err = xml.Unmarshal(decrypted, msg) 32 | //fmt.Dump(event) 33 | if err != nil { 34 | return err 35 | } 36 | // set ticket in redis 37 | err = services.OpenPlatformApp.VerifyTicket.SetTicket(msg.ComponentVerifyTicket) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | fmt.Dump(msg) 43 | case openplatform.EVENT_AUTHORIZED: 44 | 45 | } 46 | return result 47 | }) 48 | 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | err = rs.Write(context.Writer) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | } 59 | 60 | func APIOpenPlatformCallbackWithApp(context *gin.Context) { 61 | 62 | requestXML, _ := ioutil.ReadAll(context.Request.Body) 63 | context.Request.Body = ioutil.NopCloser(bytes.NewBuffer(requestXML)) 64 | println(string(requestXML)) 65 | 66 | var err error 67 | 68 | rs, err := services.OpenPlatformApp.Server.Notify(context.Request, func(event *openplatform.Callback, decrypted []byte, infoType string) (result interface{}) { 69 | 70 | result = kernel.SUCCESS_EMPTY_RESPONSE 71 | //fmt.Dump(event) 72 | msg := &openplatform.EventAuthorization{} 73 | err = xml.Unmarshal(decrypted, msg) 74 | if err != nil { 75 | return err 76 | } 77 | fmt.Dump(msg) 78 | 79 | return result 80 | }) 81 | 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | err = rs.Write(context.Writer) 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /controllers/wecom/account-service/account-service.go: -------------------------------------------------------------------------------- 1 | package account_service 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/accountService/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 添加客服帐号 11 | // https://work.weixin.qq.com/api/doc/90000/90135/94648 12 | func APIAccountServiceAccountAdd(c *gin.Context) { 13 | 14 | mediaID := c.DefaultQuery("mediaID", "294DpAog3YA5b9rTK4PjjfRfYLO0L5qpDHAJIzhhQ2jAEWjb9i661Q4lk8oFnPtmj") 15 | 16 | res, err := services.WeComApp.AccountService.Add(c.Request.Context(), "新建的客服帐号", mediaID) 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, res) 23 | } 24 | 25 | // 删除客服帐号 26 | // https://work.weixin.qq.com/api/doc/90000/90135/94663 27 | func APIAccountServiceAccountDel(c *gin.Context) { 28 | 29 | openKFID := c.DefaultQuery("openKFID", "wkAJ2GCAAAZSfhHCt7IFSvLKtMPxyJTw") 30 | 31 | res, err := services.WeComApp.AccountService.Del(c.Request.Context(), openKFID) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, res) 38 | } 39 | 40 | // 修改客服帐号 41 | // https://work.weixin.qq.com/api/doc/90000/90135/94664 42 | func APIAccountServiceAccountUpdate(c *gin.Context) { 43 | 44 | options := &request.RequestAccountUpdate{ 45 | OpenKFID: c.DefaultQuery("openKFID", "wkAJ2GCAAAZSfhHCt7IFSvLKtMPxyJTw"), 46 | Name: "修改客服名", 47 | MediaID: c.DefaultQuery("mediaID", "294DpAog3YA5b9rTK4PjjfRfYLO0L5qpDHAJIzhhQ2jAEWjb9i661Q4lk8oFnPtmj"), 48 | } 49 | 50 | res, err := services.WeComApp.AccountService.Update(c.Request.Context(), options) 51 | 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | c.JSON(http.StatusOK, res) 57 | } 58 | 59 | // 获取客服帐号列表 60 | // https://work.weixin.qq.com/api/doc/90000/90135/94661 61 | func APIAccountServiceAccountList(c *gin.Context) { 62 | 63 | res, err := services.WeComApp.AccountService.List(c.Request.Context()) 64 | 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | c.JSON(http.StatusOK, res) 70 | } 71 | 72 | // 获取客服帐号链接 73 | // https://work.weixin.qq.com/api/doc/90000/90135/94665 74 | func APIAccountServiceAddContactWay(c *gin.Context) { 75 | 76 | openKFID := c.DefaultQuery("openKFID", "wkAJ2GCAAAZSfhHCt7IFSvLKtMPxyJTw") 77 | scene := c.DefaultQuery("scene", "1234") 78 | 79 | res, err := services.WeComApp.AccountService.AddContactWay(c.Request.Context(), openKFID, scene) 80 | 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | c.JSON(http.StatusOK, res) 86 | } 87 | -------------------------------------------------------------------------------- /controllers/wecom/account-service/customer.go: -------------------------------------------------------------------------------- 1 | package account_service 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/accountService/customer/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 读取消息 11 | // https://work.weixin.qq.com/api/doc/90000/90135/94670 12 | func APIAccountServiceCustomerBatchGet(c *gin.Context) { 13 | externalUserIDList := []string{c.DefaultQuery("externalUserIDList", "matrix-x")} 14 | 15 | res, err := services.WeComApp.AccountServiceCustomer.BatchGet(c.Request.Context(), externalUserIDList) 16 | 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | c.JSON(http.StatusOK, res) 22 | } 23 | 24 | // 获取配置的专员与客户群 25 | // https://work.weixin.qq.com/api/doc/90000/90135/94674 26 | func APIAccountServiceCustomerGetUpgradeServiceConfig(c *gin.Context) { 27 | res, err := services.WeComApp.AccountServiceCustomer.GetUpgradeServiceConfig(c.Request.Context()) 28 | 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | c.JSON(http.StatusOK, res) 34 | } 35 | 36 | // 为客户升级为专员或客户群服务 37 | // https://work.weixin.qq.com/api/doc/90000/90135/94674 38 | func APIAccountServiceCustomerUpgradeService(c *gin.Context) { 39 | options := &request.RequestUpgradeService{ 40 | OpenKFID: c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx"), 41 | ExternalUserID: c.DefaultQuery("externalUserID", "kfxxxxxxxxxxxxxx"), 42 | Type: 2, // 表示是升级到专员服务还是客户群服务。1:专员服务。2:客户群服务 43 | Member: &request.RequestUpgradeServiceMember{ 44 | UserID: c.DefaultQuery("member", "matrix-x"), 45 | Wording: "你好,我是你的专属服务专员zhangsan", 46 | }, // 推荐的服务专员,type等于1时有效 47 | GroupChat: &request.RequestUpgradeServiceGroupChat{ 48 | ChatID: "wraaaaaaaaaaaaaaaa", 49 | Wording: "欢迎加入你的专属服务群", 50 | }, // 推荐的客户群,type等于2时有效 51 | } 52 | res, err := services.WeComApp.AccountServiceCustomer.UpgradeService(c.Request.Context(), options) 53 | 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | c.JSON(http.StatusOK, res) 59 | } 60 | 61 | // 为客户取消推荐 62 | // https://work.weixin.qq.com/api/doc/90000/90135/94674 63 | func APIAccountServiceCustomerCancelUpgradeService(c *gin.Context) { 64 | openKFID := c.DefaultQuery("openKFID", "kfxxxxxxxxxxxxxx") 65 | externalUserID := c.DefaultQuery("externalUserID", "kfxxxxxxxxxxxxxx") 66 | res, err := services.WeComApp.AccountServiceCustomer.CancelUpgradeService(c.Request.Context(), openKFID, externalUserID) 67 | 68 | if err != nil { 69 | panic(err) 70 | } 71 | 72 | c.JSON(http.StatusOK, res) 73 | } 74 | -------------------------------------------------------------------------------- /controllers/wecom/user/tag.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | const defaultTagId = 100 10 | 11 | // APITagCreate 创建标签 12 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90210 13 | func APITagCreate(c *gin.Context) { 14 | res, err := services.WeComApp.UserTag.Create(c.Request.Context(), "TestTag", defaultTagId) 15 | if err != nil { 16 | panic(err) 17 | } 18 | c.JSON(http.StatusOK, res) 19 | } 20 | 21 | // APITagUpdate 更新标签名字 22 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90211 23 | func APITagUpdate(c *gin.Context) { 24 | res, err := services.WeComApp.UserTag.Update(c.Request.Context(), "TestTag1", defaultTagId) 25 | if err != nil { 26 | panic(err) 27 | } 28 | c.JSON(http.StatusOK, res) 29 | } 30 | 31 | // APITagDelete 删除标签 32 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90212 33 | func APITagDelete(c *gin.Context) { 34 | res, err := services.WeComApp.UserTag.Delete(c.Request.Context(), defaultTagId) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | c.JSON(http.StatusOK, res) 40 | } 41 | 42 | // TagList 获取标签列表 43 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90216 44 | func APITagList(c *gin.Context) { 45 | res, err := services.WeComApp.UserTag.List(c.Request.Context()) 46 | if err != nil { 47 | panic(err) 48 | } 49 | c.JSON(http.StatusOK, res) 50 | } 51 | 52 | // TagUserGet 获取标签成员 53 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90213 54 | func APITagUserGet(c *gin.Context) { 55 | res, err := services.WeComApp.UserTag.Get(c.Request.Context(), defaultTagId) 56 | if err != nil { 57 | panic(err) 58 | } 59 | c.JSON(http.StatusOK, res) 60 | } 61 | 62 | // TagUserAdd 增加标签成员 63 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90214 64 | func APITagUserAdd(c *gin.Context) { 65 | //tagId := c.DefaultQuery("tagId", string(rune(defaultTagId))) 66 | userId := c.DefaultQuery("tagId", "walle") 67 | res, err := services.WeComApp.UserTag.TagUsers(c.Request.Context(), defaultTagId, []string{userId}) 68 | if err != nil { 69 | panic(err) 70 | } 71 | c.JSON(http.StatusOK, res) 72 | } 73 | 74 | // TagUserDel 删除标签成员 75 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90215 76 | func APITagUserDel(c *gin.Context) { 77 | //tagId := c.DefaultQuery("tagId", string(rune(defaultTagId))) 78 | userId := c.DefaultQuery("tagId", "walle") 79 | res, err := services.WeComApp.UserTag.TagUsers(c.Request.Context(), defaultTagId, []string{userId}) 80 | if err != nil { 81 | panic(err) 82 | } 83 | c.JSON(http.StatusOK, res) 84 | } 85 | -------------------------------------------------------------------------------- /templates/h5-pay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Wechat Payment 7 | 8 | 9 | 12 | 13 | 14 | 15 |
16 | 17 |
18 |
JSSDK支付
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 开始支付 31 | 74 | 75 | -------------------------------------------------------------------------------- /controllers/miniprogram/plugin-manager.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | ) 8 | 9 | // 向插件开发者发起使用插件的申请 10 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.applyPlugin.html 11 | func APIPluginManagerApplyPlugin(c *gin.Context) { 12 | 13 | pluginAppID, exist := c.GetQuery("pluginAppID") 14 | if !exist { 15 | panic("parameter plugin app id expected") 16 | } 17 | 18 | rs, err := services.MiniProgramApp.Plugin.ApplyPlugin(c.Request.Context(), pluginAppID, "test reason") 19 | 20 | if err != nil { 21 | panic(err) 22 | } 23 | 24 | c.JSON(http.StatusOK, rs) 25 | } 26 | 27 | // 获取当前所有插件使用方(供插件开发者调用) 28 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.getPluginDevApplyList.html 29 | func APIPluginManagerGetPluginDevApplyList(c *gin.Context) { 30 | 31 | rs, err := services.MiniProgramApp.Plugin.GetPluginDevApplyList(c.Request.Context(), "dev_apply_list", 1, 1) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, rs) 38 | } 39 | 40 | // 查询已添加的插件 41 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.getPluginList.html 42 | func APIPluginManagerGetPluginList(c *gin.Context) { 43 | 44 | rs, err := services.MiniProgramApp.Plugin.GetPluginList(c.Request.Context()) 45 | 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | c.JSON(http.StatusOK, rs) 51 | } 52 | 53 | // 修改插件使用申请的状态(供插件开发者调用) 54 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.setDevPluginApplyStatus.html 55 | func APIPluginManagerSetDevPluginApplyStatus(c *gin.Context) { 56 | 57 | pluginAppID, exist := c.GetQuery("pluginAppID") 58 | if !exist { 59 | panic("parameter plugin app id expected") 60 | } 61 | 62 | rs, err := services.MiniProgramApp.Plugin.SetDevPluginApplyStatus(c.Request.Context(), "dev_agree", pluginAppID, "test reason") 63 | 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | c.JSON(http.StatusOK, rs) 69 | } 70 | 71 | // 删除已添加的插件 72 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/plugin-management/pluginManager.unbindPlugin.html 73 | func APIPluginManagerUnbindPlugin(c *gin.Context) { 74 | 75 | pluginAppID, exist := c.GetQuery("pluginAppID") 76 | if !exist { 77 | panic("parameter plugin app id expected") 78 | } 79 | 80 | rs, err := services.MiniProgramApp.Plugin.UnbindPlugin(c.Request.Context(), pluginAppID) 81 | 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | c.JSON(http.StatusOK, rs) 87 | } 88 | -------------------------------------------------------------------------------- /controllers/wecom/oa/calendar.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | "strconv" 9 | ) 10 | 11 | // 创建日历 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93647 13 | func APICalendarAdd(c *gin.Context) { 14 | agentId := c.DefaultQuery("agentID", "1000014") 15 | agentID, _ := strconv.Atoi(agentId) 16 | 17 | calendar := &power.HashMap{ 18 | "organizer": "userid1", 19 | "readonly": 1, 20 | "set_as_default": 1, 21 | "summary": "test_summary", 22 | "color": "#FF3030", 23 | "description": "test_describe", 24 | "shares": []power.HashMap{ 25 | power.HashMap{ 26 | "userid": "userid2", 27 | }, 28 | power.HashMap{ 29 | "userid": "userid3", 30 | "readonly": 1, 31 | }, 32 | }, 33 | } 34 | 35 | res, err := services.WeComApp.OACalendar.Add(c.Request.Context(), calendar, agentID) 36 | 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | c.JSON(http.StatusOK, res) 42 | } 43 | 44 | // 更新日历 45 | // https://work.weixin.qq.com/api/doc/90000/90135/93647 46 | func APICalendarUpdate(c *gin.Context) { 47 | 48 | calendar := &power.HashMap{ 49 | "cal_id": "wcjgewCwAAqeJcPI1d8Pwbjt7nttzAAA", 50 | "readonly": 1, 51 | "summary": "test_summary", 52 | "color": "#FF3030", 53 | "description": "test_describe_1", 54 | "shares": []power.HashMap{ 55 | power.HashMap{ 56 | "userid": "userid1", 57 | }, 58 | power.HashMap{ 59 | "userid": "userid2", 60 | "readonly": 1, 61 | }, 62 | }, 63 | } 64 | 65 | res, err := services.WeComApp.OACalendar.Update(c.Request.Context(), calendar) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, res) 72 | } 73 | 74 | // 获取日历详情 75 | // https://work.weixin.qq.com/api/doc/90000/90135/93647 76 | func APICalendarGet(c *gin.Context) { 77 | calIDList := []string{ 78 | c.DefaultQuery("calID", "wcjgewCwAAqeJcPI1d8Pwbjt7nttzAAA"), 79 | } 80 | res, err := services.WeComApp.OACalendar.Get(c.Request.Context(), calIDList) 81 | 82 | if err != nil { 83 | panic(err) 84 | } 85 | 86 | c.JSON(http.StatusOK, res) 87 | } 88 | 89 | // 删除日历 90 | // https://work.weixin.qq.com/api/doc/90000/90135/93647 91 | func APICalendarDel(c *gin.Context) { 92 | calID := c.DefaultQuery("calID", "wcjgewCwAAqeJcPI1d8Pwbjt7nttzAAA") 93 | res, err := services.WeComApp.OACalendar.Del(c.Request.Context(), calID) 94 | 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | c.JSON(http.StatusOK, res) 100 | } 101 | -------------------------------------------------------------------------------- /controllers/wecom/user/department.go: -------------------------------------------------------------------------------- 1 | package user 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/department/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | "strconv" 9 | ) 10 | 11 | const defaultDepartmentId = 3000001 12 | 13 | // APIDepartmentCreate 创建部门 14 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90205 15 | func APIDepartmentCreate(c *gin.Context) { 16 | name := c.DefaultQuery("name", "IT支持部") 17 | idStr := c.DefaultQuery("id", string(rune(defaultDepartmentId))) 18 | id, _ := strconv.Atoi(idStr) 19 | 20 | res, err := services.WeComContactApp.Department.Create(c.Request.Context(), &request.RequestDepartmentInsert{ 21 | Name: name, 22 | NameEn: "", 23 | ParentID: 0, 24 | Order: 0, 25 | ID: id, 26 | }) 27 | 28 | if err != nil { 29 | panic(err) 30 | } 31 | 32 | c.JSON(http.StatusOK, res) 33 | } 34 | 35 | // APIDepartmentUpdate 更新部门 36 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90206 37 | func APIDepartmentUpdate(c *gin.Context) { 38 | name := c.DefaultQuery("name", "IT支持部1") 39 | idStr := c.DefaultQuery("id", string(rune(defaultDepartmentId))) 40 | id, _ := strconv.Atoi(idStr) 41 | res, err := services.WeComContactApp.Department.Update(c.Request.Context(), &request.RequestDepartmentUpdate{ 42 | Name: name, 43 | ParentID: id, 44 | }) 45 | 46 | if err != nil { 47 | panic(err) 48 | } 49 | 50 | c.JSON(http.StatusOK, res) 51 | } 52 | 53 | // APIDepartmentDelete 删除部门 54 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90207 55 | func APIDepartmentDelete(c *gin.Context) { 56 | idStr := c.DefaultQuery("id", string(rune(defaultDepartmentId))) 57 | id, _ := strconv.Atoi(idStr) 58 | res, err := services.WeComContactApp.Department.Delete(c.Request.Context(), id) 59 | 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | c.JSON(http.StatusOK, res) 65 | } 66 | 67 | // APIDepartmentList 获取部门列表 68 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90208 69 | func APIDepartmentList(c *gin.Context) { 70 | idStr := c.DefaultQuery("id", "0") 71 | id, _ := strconv.Atoi(idStr) 72 | // 0 表示获取公司所有部门 73 | res, err := services.WeComContactApp.Department.List(c.Request.Context(), id) 74 | 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | c.JSON(http.StatusOK, res) 80 | } 81 | 82 | // APIDepartmentSimpleList 获取子部门ID列表 83 | // https://open.work.weixin.qq.com/api/doc/90000/90135/90208 84 | func APIDepartmentSimpleList(c *gin.Context) { 85 | idStr := c.DefaultQuery("id", "0") 86 | id, _ := strconv.Atoi(idStr) 87 | // 0 表示获取企业所有部门 88 | res, err := services.WeComContactApp.Department.SimpleList(c.Request.Context(), id) 89 | 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | c.JSON(http.StatusOK, res) 95 | } 96 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "description": "这是一个开源的使用教程,包含PowerWechat的大部分接口调试代码.", 5 | "title": "PowerWechat API Docs", 6 | "termsOfService": "http://artisan-cloud.com/terms/", 7 | "contact": { 8 | "name": "ArtisanCloud Support", 9 | "url": "https://powerwechat.artisan-cloud.com/zh/start/qa.html", 10 | "email": "matrix-x@artisan-cloud.como" 11 | }, 12 | "license": { 13 | "name": "MIT 2.0", 14 | "url": "https://github.com/ArtisanCloud/PowerWeChat?tab=MIT-1-ov-file#readme" 15 | }, 16 | "version": "1.0.1" 17 | }, 18 | "host": "localhost:8080", 19 | "basePath": "/api/v1", 20 | "paths": { 21 | "/clearQuota": { 22 | "get": { 23 | "description": "\nSDK产品接口的代码展示:\n```\n返回类型定义如下:\ntype ResponseOfficialAccount struct {\n\tResponseBase\n\n\tErrCode int `json:\"errcode,omitempty\"`\n\tErrMsg string `json:\"errmsg,omitempty\"`\n\n\tResultCode string `json:\"resultcode,omitempty\"`\n\tResultMsg string `json:\"resultmsg,omitempty\"`\n}\n\n具体使用接口方式:\nfunc ClearQuota(ctx *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.ClearQuota(ctx.Request.Context())\n\tif err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, data)\n}\n```", 24 | "tags": [ 25 | "OfficialAccount.base.ClearQuota" 26 | ], 27 | "summary": "公众号里清空api的调用quota:https://developers.weixin.qq.com/doc/offiaccount/openApi/clear_quota.html", 28 | "responses": {} 29 | } 30 | }, 31 | "/getCallbackIp": { 32 | "get": { 33 | "description": "\nSDK产品接口的代码展示:\n```\n返回类型定义如下:\ntype ResponseGetCallBackIP struct {\n\tresponse.ResponseOfficialAccount\n\n\tIPList []string `json:\"ip_list\"`\n}\n\n具体使用接口方式:\nfunc GetCallbackIP(ctx *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.GetCallbackIP(ctx.Request.Context())\n\tif err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, data)\n}\n```", 34 | "tags": [ 35 | "OfficialAccount.base.GetCallbackIP" 36 | ], 37 | "summary": "获取公众号回调的IP地址:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html", 38 | "responses": {} 39 | } 40 | } 41 | }, 42 | "securityDefinitions": { 43 | "BasicAuth": { 44 | "type": "basic" 45 | } 46 | }, 47 | "externalDocs": { 48 | "description": "OpenAPI", 49 | "url": "https://swagger.io/resources/open-api/" 50 | } 51 | } -------------------------------------------------------------------------------- /controllers/wecom/oa/meeting.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/meeting/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 创建预约会议 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93627 13 | func APIMeetingCreate(c *gin.Context) { 14 | 15 | options := &request.RequestMeetingCreate{ 16 | CreatorUserID: "zhangsan", 17 | Title: "新建会议", 18 | MeetingStart: 1600000000, 19 | MeetingDuration: 3600, 20 | Description: "新建会议描述", 21 | Type: 1, 22 | RemindTime: 60, 23 | AgentID: 1000014, 24 | Attendees: &power.HashMap{ 25 | "userid": []string{ 26 | "lisi", 27 | "wangwu", 28 | }, 29 | "external_userid": []string{ 30 | "woabc", 31 | "woced", 32 | }, 33 | "device_sn": []string{ 34 | "devsn1", 35 | "devsn2", 36 | }, 37 | }, 38 | } 39 | res, err := services.WeComApp.OAMeeting.Create(c.Request.Context(), options) 40 | 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | c.JSON(http.StatusOK, res) 46 | } 47 | 48 | func APIMeetingUpdate(c *gin.Context) { 49 | 50 | options := &request.RequestMeetingUpdate{ 51 | MeetingID: "XXXXXXXXX", 52 | Title: "新需求", 53 | MeetingStart: 1600000000, 54 | MeetingDuration: 10000, 55 | Description: "test", 56 | Type: 1, 57 | RemindTime: 60, 58 | Attendees: &power.HashMap{ 59 | "userid": []string{ 60 | "lisi", 61 | "wangwu", 62 | }, 63 | 64 | "external_userid": []string{ 65 | "woabc", 66 | "woced", 67 | }, 68 | "device_sn": []string{ 69 | "devsn1", 70 | "devsn2", 71 | }, 72 | }, 73 | } 74 | res, err := services.WeComApp.OAMeeting.Update(c.Request.Context(), options) 75 | 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | c.JSON(http.StatusOK, res) 81 | } 82 | 83 | func APIMeetingCancel(c *gin.Context) { 84 | meetingID := c.DefaultQuery("meetingID", "xxxxxx") 85 | res, err := services.WeComApp.OAMeeting.Cancel(c.Request.Context(), meetingID) 86 | 87 | if err != nil { 88 | panic(err) 89 | } 90 | 91 | c.JSON(http.StatusOK, res) 92 | } 93 | 94 | func APIMeetingGetUserMeetingID(c *gin.Context) { 95 | 96 | userID := c.DefaultQuery("userID", "") 97 | cursor := "cursor" 98 | beginTime := int64(1586136317) 99 | endTime := int64(1586236317) 100 | limit := 100 101 | 102 | res, err := services.WeComApp.OAMeeting.GetUserMeetingID(c.Request.Context(), userID, cursor, beginTime, endTime, limit) 103 | 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | c.JSON(http.StatusOK, res) 109 | } 110 | -------------------------------------------------------------------------------- /controllers/wecom/media.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "io/ioutil" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 上传临时素材 12 | // https://work.weixin.qq.com/api/doc/90000/90135/90253 13 | func APIMediaUploadByURL(c *gin.Context) { 14 | mediaPath := "./resource/qrcode.png" 15 | rs, err := services.WeComApp.Media.UploadTempFile(c.Request.Context(), mediaPath, nil) 16 | 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | c.JSON(http.StatusOK, rs) 22 | } 23 | 24 | // 上传临时素材 25 | // https://work.weixin.qq.com/api/doc/90000/90135/90253 26 | func APIMediaUploadByData(c *gin.Context) { 27 | var err error 28 | mediaPath := "./resource/cloud.jpg" 29 | value, err := ioutil.ReadFile(mediaPath) 30 | 31 | rs, err := services.WeComApp.Media.UploadTempFile(c.Request.Context(), "", &power.HashMap{ 32 | "name": "temp.jpg", // 请确保文件名有准确的文件类型 33 | "value": value, 34 | }) 35 | 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | c.JSON(http.StatusOK, rs) 41 | } 42 | 43 | // 上传图片 44 | // https://work.weixin.qq.com/api/doc/90000/90135/90256 45 | func APIMediaUploadImgByPath(c *gin.Context) { 46 | var err error 47 | mediaPath := "./resource/cloud.jpg" 48 | 49 | rs, err := services.WeComApp.Media.UploadImage(c.Request.Context(), mediaPath, nil) 50 | 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | c.JSON(http.StatusOK, rs) 56 | } 57 | 58 | // 上传图片 59 | // https://work.weixin.qq.com/api/doc/90000/90135/90256 60 | func APIMediaUploadImgByData(c *gin.Context) { 61 | var err error 62 | mediaPath := "./resource/cloud.jpg" 63 | value, err := ioutil.ReadFile(mediaPath) 64 | 65 | rs, err := services.WeComApp.Media.UploadImage(c.Request.Context(), "", &power.HashMap{ 66 | "name": "image.jpg", // 请确保文件名有准确的文件类型 67 | "value": value, 68 | }) 69 | 70 | if err != nil { 71 | panic(err) 72 | } 73 | 74 | c.JSON(http.StatusOK, rs) 75 | } 76 | 77 | // 获取临时素材 78 | // https://work.weixin.qq.com/api/doc/90000/90135/90254 79 | func APIMediaGet(c *gin.Context) { 80 | var err error 81 | 82 | mediaID := c.DefaultQuery("mediaID", "MEDIAID") 83 | 84 | rs, err := services.WeComApp.Media.Get(c.Request.Context(), mediaID) 85 | 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | c.JSON(http.StatusOK, rs) 91 | } 92 | 93 | // 获取高清语音素材 94 | // https://work.weixin.qq.com/api/doc/90000/90135/90255 95 | func APIMediaGetJSSDK(c *gin.Context) { 96 | var err error 97 | 98 | mediaID := c.DefaultQuery("mediaID", "MEDIAID") 99 | 100 | rs, err := services.WeComApp.Media.GetJSSDK(c.Request.Context(), mediaID) 101 | 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | c.JSON(http.StatusOK, rs) 107 | } 108 | -------------------------------------------------------------------------------- /controllers/miniprogram/security.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "io/ioutil" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 同步校验图片违法违规内容 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html 13 | func APISecurityImgSecCheckByPath(c *gin.Context) { 14 | 15 | mediaPath := "./resource/cloud.jpg" 16 | rs, err := services.MiniProgramApp.Security.ImgSecCheck(c.Request.Context(), mediaPath, nil) 17 | 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | c.JSON(http.StatusOK, rs) 23 | 24 | } 25 | 26 | // 同步校验图片违法违规内容 27 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.imgSecCheck.html 28 | func APISecurityImgSecCheckByData(c *gin.Context) { 29 | 30 | var err error 31 | //mediaPath := "./resource/cloud.jpg" 32 | mediaPath := "/Users/walle/Library/Containers/com.tencent.WeWorkMac/Data/Documents/Profiles/7AC209353D1541276F36EBD6B929D4C4/Caches/Files/2022-08/a2fd7932928b4c8d9e88f44745d53391/2bc9b19229a761ab184dba7584adc1d1.jpg" 33 | value, err := ioutil.ReadFile(mediaPath) 34 | 35 | rs, err := services.MiniProgramApp.Security.ImgSecCheck(c.Request.Context(), "", &power.HashMap{ 36 | "name": "media", // 请确保文件名有准确的文件类型 37 | "value": value, 38 | }) 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | c.JSON(http.StatusOK, rs) 45 | 46 | } 47 | 48 | // 异步校验图片/音频是否含有违法违规内容 49 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.mediaCheckAsync.html 50 | func APISecurityMediaCheckAsync(c *gin.Context) { 51 | mediaURL := c.DefaultQuery("mediaURL", "https://www.w3school.com.cn/i/horse.mp3") 52 | openID, exist := c.GetQuery("openid") 53 | 54 | if !exist { 55 | panic("parameter open id expected") 56 | } 57 | 58 | rs, err := services.MiniProgramApp.Security.MediaCheckAsync( 59 | c.Request.Context(), 60 | mediaURL, 61 | 1, 62 | 2, 63 | openID, 64 | 1, 65 | ) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, rs) 72 | 73 | } 74 | 75 | // 检查一段文本是否含有违法违规内容 76 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/sec-check/security.msgSecCheck.html 77 | func APISecurityMsgSecCheck(c *gin.Context) { 78 | content := c.DefaultQuery("content", "Hello, World") 79 | openID, exist := c.GetQuery("openid") 80 | if !exist { 81 | panic("parameter open id expected") 82 | } 83 | 84 | rs, err := services.MiniProgramApp.Security.MsgSecCheck( 85 | c.Request.Context(), 86 | openID, 87 | 1, 88 | 2, 89 | content, 90 | "test name", 91 | "test title", 92 | "test sign", 93 | ) 94 | 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | c.JSON(http.StatusOK, rs) 100 | 101 | } 102 | -------------------------------------------------------------------------------- /controllers/miniprogram/near-by-poi.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | "strconv" 9 | ) 10 | 11 | // 添加地点 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/nearby-poi/nearbyPoi.add.html 13 | func APINearbyPoiAdd(c *gin.Context) { 14 | 15 | rs, err := services.MiniProgramApp.NearbyPoi.Add( 16 | c.Request.Context(), 17 | &power.HashMap{ 18 | "is_comm_nearby": "1", //值固定 19 | "kf_info": "{\"open_kf\":true,\"kf_headimg\":\"http://mmbiz.qpic.cn/mmbiz_jpg/kKMgNtnEfQzDKpLXYhgo3W3Gndl34gITqmP914zSwhajIEJzUPpx40P7R8fRe1QmicneQMhFzpZNhSLjrvU1pIA/0?wx_fmt=jpeg\",\"kf_name\":\"Harden\"}", 20 | "pic_list": "{\"list\":[\"http://mmbiz.qpic.cn/mmbiz_jpg/kKMgNtnEfQzDKpLXYhgo3W3Gndl34gITqmP914zSwhajIEJzUPpx40P7R8fRe1QmicneQMhFzpZNhSLjrvU1pIA/0?wx_fmt=jpeg\",\"http://mmbiz.qpic.cn/mmbiz_jpg/kKMgNtnEfQzDKpLXYhgo3W3Gndl34gITRneE5FS9uYruXGMmrtmhsBySwddEWUGOibG8Ze2NT5E3Dyt79I0htNg/0?wx_fmt=jpeg\"]}", 21 | "service_infos": "{\"service_infos\":[{\"id\":2,\"type\":1,\"name\":\"快递\",\"appid\":\"wx1373169e494e0c39\",\"path\":\"index\"},{\"id\":0,\"type\":2,\"name\":\"自定义\",\"appid\":\"wx1373169e494e0c39\",\"path\":\"index\"}]}", 22 | "store_name": "羊村小马烧烤", 23 | "contract_phone": "111111111", 24 | "hour": "00:00-11:11", 25 | "company_name": "深圳市腾讯计算机系统有限公司", 26 | "credential": "156718193518281", 27 | "address": "新疆维吾尔自治区克拉玛依市克拉玛依区碧水路15-1-8号(碧水云天广场)", 28 | "qualification_list": "3LaLzqiTrQcD20DlX_o-OV1-nlYMu7sdVAL7SV2PrxVyjZFZZmB3O6LPGaYXlZWq", 29 | "poi_id": "", 30 | }) 31 | 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | c.JSON(http.StatusOK, rs) 37 | } 38 | 39 | func APINearbyPoiDelete(c *gin.Context) { 40 | 41 | poiID, exist := c.GetQuery("poiID") 42 | if !exist { 43 | panic("parameter poi id expected") 44 | } 45 | 46 | rs, err := services.MiniProgramApp.NearbyPoi.Delete(c.Request.Context(), poiID) 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | c.JSON(http.StatusOK, rs) 53 | } 54 | 55 | func APINearbyPoiGetList(c *gin.Context) { 56 | 57 | strPage := c.DefaultQuery("page", "1") 58 | 59 | page, err := strconv.Atoi(strPage) 60 | 61 | strPageRows := c.DefaultQuery("pageRows", "100") 62 | 63 | pageRows, err := strconv.Atoi(strPageRows) 64 | 65 | rs, err := services.MiniProgramApp.NearbyPoi.GetList(c.Request.Context(), page, pageRows) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, rs) 72 | } 73 | 74 | func APINearbySetShowStatus(c *gin.Context) { 75 | 76 | poiID, exist := c.GetQuery("poiID") 77 | if !exist { 78 | panic("parameter poi id expected") 79 | } 80 | 81 | rs, err := services.MiniProgramApp.NearbyPoi.SetShowStatus(c.Request.Context(), poiID, 1) 82 | 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | c.JSON(http.StatusOK, rs) 88 | } 89 | -------------------------------------------------------------------------------- /config/model.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/jinzhu/configor" 5 | "log" 6 | ) 7 | 8 | type Configuration struct { 9 | Payment Payment 10 | MiniProgram MiniProgram 11 | WeCom WeCom 12 | OffiAccount OffiAccount 13 | OpenPlatform OpenPlatform 14 | } 15 | 16 | type Payment struct { 17 | AppID string `required:"true" env:"app_id"` 18 | MchID string `required:"true" env:"mch_id"` 19 | CertPath string `required:"true" env:"wx_cert_path"` 20 | KeyPath string `required:"true" env:"wx_key_path"` 21 | SerialNo string `required:"true" env:"serial_no"` 22 | CertificateKeyPath string `required:"false" env:"certificate_key_path"` 23 | WechatPaySerial string `required:"false" env:"wechat_pay_serial"` 24 | RSAPublicKeyPath string `required:"false" env:"wx_rsa_public_key_path"` 25 | MchApiV3Key string `env:"mch_api_v3_key"` 26 | Key string `env:"key"` 27 | ResponseType string `default:"map"` 28 | NotifyURL string `env:"notify_url"` 29 | SubMchID string `env:"sub_mch_id"` 30 | SubAppID string `env:"sub_app_id"` 31 | HttpDebug bool `default:"false"` 32 | RedisAddr string `env:"redis_addr"` 33 | } 34 | 35 | type MiniProgram struct { 36 | AppID string `required:"true" env:"miniprogram_app_id"` 37 | Secret string `required:"true" env:"miniprogram_secret"` 38 | RedisAddr string `env:"redis_addr"` 39 | MessageToken string `env:"message_token"` 40 | MessageAesKey string `env:"message_aes_key"` 41 | 42 | VirtualPayAppKey string `env:"virtual_pay_app_key"` 43 | VirtualPayOfferID string `env:"virtual_pay_offer_id"` 44 | } 45 | 46 | type WeCom struct { 47 | CorpID string `env:"corp_id"` 48 | AgentID int `env:"wecom_agent_id"` 49 | Secret string `env:"wecom_secret"` 50 | MessageToken string `env:"app_message_token"` 51 | MessageAesKey string `env:"app_message_aes_key"` 52 | MessageCallback string `env:"app_message_callback_url"` 53 | OAuthCallback string `env:"app_oauth_callback_url"` 54 | ContactSecret string `env:"contact_secret"` 55 | ContactToken string `env:"contact_token"` 56 | ContactAESKey string `env:"contact_aes_key"` 57 | ContactCallback string `env:"contact_callback_url"` 58 | RedisAddr string `env:"redis_addr"` 59 | } 60 | 61 | type OffiAccount struct { 62 | AppID string `required:"true" env:"appid"` 63 | AppSecret string `required:"true" env:"appsecret"` 64 | RedisAddr string `env:"redis_addr"` 65 | MessageToken string `env:"message_token"` 66 | MessageAesKey string `env:"message_aes_key"` 67 | } 68 | 69 | type OpenPlatform struct { 70 | AppID string 71 | AppSecret string 72 | MessageToken string 73 | MessageAesKey string 74 | RedisAddr string 75 | } 76 | 77 | func configFiles() []string { 78 | return []string{"config.yml"} 79 | } 80 | 81 | // Get returns the configuration extracted from env variables or config file. 82 | func Get() *Configuration { 83 | conf := new(Configuration) 84 | err := configor.New(&configor.Config{}).Load(conf, configFiles()...) 85 | if err != nil { 86 | log.Printf("%#v", conf) 87 | panic(err) 88 | } 89 | return conf 90 | } 91 | -------------------------------------------------------------------------------- /controllers/official-account/user-tag.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | "strconv" 8 | ) 9 | 10 | // UserTagList 获取用户标签列表 11 | func GetUserTagList(ctx *gin.Context) { 12 | data, err := services.OfficialAccountApp.UserTag.List(ctx.Request.Context()) 13 | if err != nil { 14 | ctx.String(http.StatusBadRequest, err.Error()) 15 | return 16 | } 17 | ctx.JSON(http.StatusOK, data) 18 | } 19 | 20 | // UserTagCreate 创建用户标签 21 | func UserTagCreate(ctx *gin.Context) { 22 | tagName, _ := ctx.GetPostForm("tagName") 23 | data, err := services.OfficialAccountApp.UserTag.Create(ctx.Request.Context(), tagName) 24 | if err != nil { 25 | ctx.String(http.StatusBadRequest, err.Error()) 26 | return 27 | } 28 | ctx.JSON(http.StatusOK, data) 29 | } 30 | 31 | // UserTagUpdate 更新用户标签 32 | func UserTagUpdate(ctx *gin.Context) { 33 | tagID, _ := ctx.GetPostForm("tagID") 34 | tagName, _ := ctx.GetPostForm("tagName") 35 | data, err := services.OfficialAccountApp.UserTag.Update(ctx.Request.Context(), tagID, tagName) 36 | if err != nil { 37 | ctx.String(http.StatusBadRequest, err.Error()) 38 | return 39 | } 40 | ctx.JSON(http.StatusOK, data) 41 | } 42 | 43 | // UserTagDelete 删除用户标签 44 | func UserTagDelete(ctx *gin.Context) { 45 | tagID, _ := ctx.GetPostForm("tagID") 46 | data, err := services.OfficialAccountApp.UserTag.Delete(ctx.Request.Context(), tagID) 47 | if err != nil { 48 | ctx.String(http.StatusBadRequest, err.Error()) 49 | return 50 | } 51 | ctx.JSON(http.StatusOK, data) 52 | } 53 | 54 | // GetUserTagsByOpenID 获取用户身上的标签列表 55 | func GetUserTagsByOpenID(ctx *gin.Context) { 56 | openID := ctx.Query("openID") 57 | data, err := services.OfficialAccountApp.UserTag.UserTags(ctx.Request.Context(), openID) 58 | if err != nil { 59 | ctx.String(http.StatusBadRequest, err.Error()) 60 | return 61 | } 62 | ctx.JSON(http.StatusOK, data) 63 | } 64 | 65 | // GetUsersOfTag 获取标签下粉丝列表 66 | func GetUsersOfTag(ctx *gin.Context) { 67 | tagID := ctx.Query("tagID") 68 | nextOpenID := ctx.Query("nextOpenID") 69 | data, err := services.OfficialAccountApp.UserTag.UsersOfTag(ctx.Request.Context(), tagID, nextOpenID) 70 | if err != nil { 71 | ctx.String(http.StatusBadRequest, err.Error()) 72 | return 73 | } 74 | ctx.JSON(http.StatusOK, data) 75 | } 76 | 77 | // UserTagBatchTagUsers 批量为用户打标签 78 | func UserTagBatchTagUsers(ctx *gin.Context) { 79 | openID := ctx.Query("openID") 80 | tagID, _ := strconv.Atoi(ctx.Query("tagID")) 81 | data, err := services.OfficialAccountApp.UserTag.TagUsers(ctx.Request.Context(), []string{openID}, tagID) 82 | if err != nil { 83 | ctx.String(http.StatusBadRequest, err.Error()) 84 | return 85 | } 86 | ctx.JSON(http.StatusOK, data) 87 | } 88 | 89 | // UserTagBatchUnTagUsers 批量为用户取消标签 90 | func UserTagBatchUnTagUsers(ctx *gin.Context) { 91 | openID := ctx.Query("openID") 92 | tagID := ctx.Query("tagID") 93 | data, err := services.OfficialAccountApp.UserTag.UntagUsers(ctx.Request.Context(), []string{openID}, tagID) 94 | if err != nil { 95 | ctx.String(http.StatusBadRequest, err.Error()) 96 | return 97 | } 98 | ctx.JSON(http.StatusOK, data) 99 | } 100 | -------------------------------------------------------------------------------- /controllers/wecom/oa/webdrive.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/webdrive/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 新建空间 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93655 13 | func APIWebDriveSpaceCreate(c *gin.Context) { 14 | options := &request.RequestWebDriveSpaceCreate{ 15 | UserID: c.DefaultQuery("userID", "USERID"), 16 | SpaceName: c.DefaultQuery("spaceName", "SPACE_NAME"), 17 | AuthInfo: []*power.HashMap{ 18 | &power.HashMap{ 19 | "type": 1, 20 | "userid": "USERID", 21 | "auth": 2, 22 | }, 23 | &power.HashMap{ 24 | "type": 2, 25 | "departmentid": "DEPARTMENTID", 26 | "auth": 1, 27 | }, 28 | }, 29 | } 30 | 31 | res, err := services.WeComApp.OAWebDrive.SpaceCreate(c.Request.Context(), options) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, res) 38 | } 39 | 40 | // 添加成员/部门 41 | // https://work.weixin.qq.com/api/doc/90000/90135/93656 42 | func APIWebDriveSpaceAcAdd(c *gin.Context) { 43 | 44 | options := &request.RequestWebDriveSpaceACLAdd{ 45 | UserID: c.DefaultQuery("userID", "USERID"), 46 | SpaceID: c.DefaultQuery("spaceName", "SPACE_NAME"), 47 | AuthInfo: []*power.HashMap{ 48 | &power.HashMap{ 49 | "type": 1, 50 | "userid": "USERID", 51 | "auth": 2, 52 | }, 53 | &power.HashMap{ 54 | "type": 2, 55 | "departmentid": "DEPARTMENTID", 56 | "auth": 1, 57 | }, 58 | }, 59 | } 60 | res, err := services.WeComApp.OAWebDrive.SpaceACLAdd(c.Request.Context(), options) 61 | 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | c.JSON(http.StatusOK, res) 67 | } 68 | 69 | // 获取文件列表 70 | // https://work.weixin.qq.com/api/doc/90000/90135/93657 71 | func APIWebDriveFileList(c *gin.Context) { 72 | 73 | options := &request.RequestWebDriveFileList{ 74 | UserID: c.DefaultQuery("userID", "USERID"), 75 | SpaceID: c.DefaultQuery("spaceID", "SPACEID"), 76 | FatherID: c.DefaultQuery("fatherID", "FATHERID"), 77 | SortType: 1, 78 | Start: 1, 79 | Limit: 20, 80 | } 81 | 82 | res, err := services.WeComApp.OAWebDrive.FileList(c.Request.Context(), options) 83 | 84 | if err != nil { 85 | panic(err) 86 | } 87 | 88 | c.JSON(http.StatusOK, res) 89 | } 90 | 91 | // 新增指定人 92 | // https://work.weixin.qq.com/api/doc/90000/90135/93658 93 | func APIWebDriveFileAclAdd(c *gin.Context) { 94 | 95 | options := &request.RequestWebDriveFileACLAdd{ 96 | UserID: c.DefaultQuery("userID", "USERID"), 97 | FileID: c.DefaultQuery("fileID", "FILEID"), 98 | AuthInfo: []*power.HashMap{ 99 | &power.HashMap{ 100 | "type": 1, 101 | "userid": "USERID", 102 | "auth": 2, 103 | }, 104 | &power.HashMap{ 105 | "type": 2, 106 | "departmentid": "DEPARTMENTID", 107 | "auth": 1, 108 | }, 109 | }, 110 | } 111 | 112 | res, err := services.WeComApp.OAWebDrive.FileACLAdd(c.Request.Context(), options) 113 | 114 | if err != nil { 115 | panic(err) 116 | } 117 | 118 | c.JSON(http.StatusOK, res) 119 | } 120 | -------------------------------------------------------------------------------- /controllers/official-account/user.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/user/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // GetUserInfo 获取单个用户信息 11 | func GetUserInfo(ctx *gin.Context) { 12 | openID := ctx.Query("openID") 13 | lang := ctx.Query("lang") 14 | data, err := services.OfficialAccountApp.User.Get(ctx.Request.Context(), openID, lang) 15 | if err != nil { 16 | ctx.String(http.StatusBadRequest, err.Error()) 17 | return 18 | } 19 | ctx.JSON(http.StatusOK, data) 20 | } 21 | 22 | // 获取多个用户信息 23 | func GetBatchUserInfo(ctx *gin.Context) { 24 | openID := ctx.Query("openID") 25 | data, err := services.OfficialAccountApp.User.BatchGet(ctx.Request.Context(), &request.RequestBatchGetUserInfo{ 26 | UserList: []*request.UserList{ 27 | { 28 | Openid: openID, 29 | }, 30 | }, 31 | }) 32 | if err != nil { 33 | ctx.String(http.StatusBadRequest, err.Error()) 34 | return 35 | } 36 | ctx.JSON(http.StatusOK, data) 37 | } 38 | 39 | // GetUserList 获取用户列表 40 | func GetUserList(ctx *gin.Context) { 41 | nextOpenId := ctx.Query("nextOpenId") 42 | data, err := services.OfficialAccountApp.User.List(ctx.Request.Context(), nextOpenId) 43 | if err != nil { 44 | ctx.String(http.StatusBadRequest, err.Error()) 45 | return 46 | } 47 | ctx.JSON(http.StatusOK, data) 48 | } 49 | 50 | // UserRemark 修改用户备注 51 | func UserRemark(ctx *gin.Context) { 52 | openID := ctx.Query("openID") 53 | remark := ctx.Query("remark") 54 | data, err := services.OfficialAccountApp.User.Remark(ctx.Request.Context(), openID, remark) 55 | if err != nil { 56 | ctx.String(http.StatusBadRequest, err.Error()) 57 | return 58 | } 59 | ctx.JSON(http.StatusOK, data) 60 | } 61 | 62 | // UserBlock 拉黑用户 63 | func UserBlock(ctx *gin.Context) { 64 | openID := ctx.Query("openID") 65 | data, err := services.OfficialAccountApp.User.Block(ctx.Request.Context(), []string{openID}) 66 | if err != nil { 67 | ctx.String(http.StatusBadRequest, err.Error()) 68 | return 69 | } 70 | ctx.JSON(http.StatusOK, data) 71 | } 72 | 73 | // UserUnBlock 取消拉黑用户 74 | func UserUnBlock(ctx *gin.Context) { 75 | openID := ctx.Query("openID") 76 | data, err := services.OfficialAccountApp.User.Unblock(ctx.Request.Context(), []string{openID}) 77 | if err != nil { 78 | ctx.String(http.StatusBadRequest, err.Error()) 79 | return 80 | } 81 | ctx.JSON(http.StatusOK, data) 82 | } 83 | 84 | // GetUserBlacklist 获取用户列表 85 | func GetUserBlacklist(ctx *gin.Context) { 86 | beginOpenid := ctx.Query("beginOpenid") 87 | data, err := services.OfficialAccountApp.User.Blacklist(ctx.Request.Context(), beginOpenid) 88 | if err != nil { 89 | ctx.String(http.StatusBadRequest, err.Error()) 90 | return 91 | } 92 | ctx.JSON(http.StatusOK, data) 93 | } 94 | 95 | // UserChangeOpenID 账号迁移 openid 转换 96 | func UserChangeOpenID(ctx *gin.Context) { 97 | oldAppId := ctx.Query("oldAppId") 98 | data, err := services.OfficialAccountApp.User.ChangeOpenID(ctx.Request.Context(), oldAppId, []string{}) 99 | if err != nil { 100 | ctx.String(http.StatusBadRequest, err.Error()) 101 | return 102 | } 103 | ctx.JSON(http.StatusOK, data) 104 | } 105 | -------------------------------------------------------------------------------- /controllers/miniprogram/auth.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "crypto/sha256" 5 | "fmt" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/base/request" 7 | "github.com/gin-gonic/gin" 8 | "log" 9 | "net/http" 10 | "power-wechat-tutorial/services" 11 | ) 12 | 13 | func APISNSSession(c *gin.Context) { 14 | 15 | code, exist := c.GetQuery("code") 16 | if !exist { 17 | panic("parameter code expected") 18 | } 19 | rs, err := services.MiniProgramApp.Auth.Session(c.Request.Context(), code) 20 | services.MiniProgramApp.AccessToken.Refresh(c.Request.Context()) 21 | print(rs) 22 | 23 | //{"session_key":"7o91EfeHO4PEnoeVzJxvqw==","openid":"o4QEk5Kc_y8QTrENCpKoxYhS4jkg","unionid":"orLIIs_Tfr0DLoG2iMwSq7RuaYRg"} 24 | 25 | encrypt := "DRdLarcsm8c2jsdSOubaLWh/FuNgC38OszQly2n5OTchOMdTMJb6FqwfSKD3PV3B5UKfljlwpkdQxFAd3q8orqfBK/X9+lth5zXvSZ+OxICncxqbycpJFf+o695N2aCE66oC+GPebSpYld0aiUYtbPZQdwr1uqg1toCIDkZCKuuXkFFl5QeKgwg6VJVZk5w5IM2mDzAnUr8pIQllvlA/4A==" 26 | sessionKey := "7o91EfeHO4PEnoeVzJxvqw==" 27 | //openId:="o4QEk5Kc_y8QTrENCpKoxYhS4jkg" 28 | //unionId:="orLIIs_Tfr0DLoG2iMwSq7RuaYRg" 29 | iv := "JhojkHhkdgqHPkqD9uYZYQ==" 30 | rs_e, err_e := services.MiniProgramApp.Encryptor.DecryptData(encrypt, sessionKey, iv) 31 | //services.MiniProgramApp.Base.CheckEncryptedData(c.Request.Context()) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | if err_e != nil { 38 | panic(err_e) 39 | } 40 | 41 | c.JSON(http.StatusOK, rs_e) 42 | 43 | } 44 | 45 | func APIResetUserSessionKey(c *gin.Context) { 46 | 47 | sessionKey := "7o91EfeHO4PEnoeVzJxvqw==" 48 | openId := "o4QEk5Kc_y8QTrENCpKoxYhS4jkg" 49 | 50 | rs, err := services.MiniProgramApp.Auth.ResetUserSessionKey(c.Request.Context(), openId, sessionKey) 51 | 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | c.JSON(http.StatusOK, rs) 57 | 58 | } 59 | 60 | func APICheckSession(c *gin.Context) { 61 | 62 | sessionKey := "7o91EfeHO4PEnoeVzJxvqw==" 63 | openId := "o4QEk5Kc_y8QTrENCpKoxYhS4jkg" 64 | 65 | rs, err := services.MiniProgramApp.Auth.CheckSession(c.Request.Context(), openId, sessionKey) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, rs) 72 | 73 | } 74 | 75 | func APICheckEncryptedData(c *gin.Context) { 76 | encryptedData := c.DefaultQuery("encryptedData", "sTWzm26PrbsXlSA8AoW+GpiyNLJP0H5p2UT4dXKwLSvXv8aU4wIiJcZUcM/IzNXnoFtERY3BDRbZh6bwd0ZGENVhucqDPXmchTqseryIZnJiKsiNMHCpAkCA2Yl00q4UpOZYtGMuTX5BTuo1yB3bOOuIfDu6neHV3D158CofGB9m7TxFQ8A/JcauWzhvmEAPygfFaqCgDTEmluLu7S8wMA==") 77 | hashByte := sha256.Sum256([]byte(encryptedData)) 78 | hash := hashByte[:] 79 | rs, err := services.MiniProgramApp.Base.CheckEncryptedData(c.Request.Context(), fmt.Sprintf("%x", hash)) 80 | 81 | if err != nil { 82 | panic(err) 83 | } 84 | 85 | c.JSON(http.StatusOK, rs) 86 | 87 | } 88 | 89 | func APIGetPaidUnionID(c *gin.Context) { 90 | openid := c.DefaultQuery("openid", "") 91 | log.Printf("openid: %s\n", openid) 92 | rs, err := services.MiniProgramApp.Base.GetPaidUnionID(c.Request.Context(), &request.RequestGetPaidUnionID{ 93 | OpenID: openid, 94 | // TransactionID: "", 95 | // MchID: "", 96 | // OutTradeNo: "", 97 | }) 98 | 99 | if err != nil { 100 | panic(err) 101 | } 102 | 103 | c.JSON(http.StatusOK, rs) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /controllers/official-account/server.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerLibs/v3/http/helper" 6 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel" 7 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/contract" 8 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/messages" 9 | models2 "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/models" 10 | "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/server/handlers/models" 11 | "github.com/gin-gonic/gin" 12 | "power-wechat-tutorial/services" 13 | ) 14 | 15 | // 回调配置 16 | // https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html 17 | func CallbackVerify(c *gin.Context) { 18 | rs, err := services.OfficialAccountApp.Server.VerifyURL(c.Request) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | // 选择1 24 | //text, _ := ioutil.ReadAll(rs.Body) 25 | //c.String(http.StatusOK, string(text)) 26 | 27 | // 选择2 28 | err = helper.HttpResponseSend(rs, c.Writer) 29 | 30 | } 31 | 32 | // 回调配置 33 | // https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html 34 | func CallbackNotify(c *gin.Context) { 35 | 36 | //requestXML, _ := io.ReadAll(c.Request.Body) 37 | //c.Request.Body = io.NopCloser(bytes.NewBuffer(requestXML)) 38 | //contentType := c.Request.Header.Get("Content-Type") 39 | // 40 | //println(contentType) 41 | //println(string(requestXML)) 42 | 43 | rs, err := services.OfficialAccountApp.Server.Notify(c.Request, func(event contract.EventInterface) interface{} { 44 | fmt.Dump("event", event) 45 | //return "handle callback" 46 | 47 | switch event.GetMsgType() { 48 | 49 | case models2.CALLBACK_MSG_TYPE_EVENT: 50 | switch event.GetEvent() { 51 | case models.CALLBACK_EVENT_SUBSCRIBE: 52 | msg := models.EventSubscribe{} 53 | err := event.ReadMessage(&msg) 54 | if err != nil { 55 | println(err.Error()) 56 | return "error" 57 | } 58 | fmt.Dump(msg) 59 | return kernel.SUCCESS_EMPTY_RESPONSE 60 | 61 | case models.CALLBACK_EVENT_UNSUBSCRIBE: 62 | msg := models.EventUnSubscribe{} 63 | err := event.ReadMessage(&msg) 64 | if err != nil { 65 | println(err.Error()) 66 | return "error" 67 | } 68 | fmt.Dump(msg) 69 | return kernel.SUCCESS_EMPTY_RESPONSE 70 | 71 | } 72 | 73 | case models2.CALLBACK_MSG_TYPE_TEXT: 74 | msg := models.MessageText{} 75 | err := event.ReadMessage(&msg) 76 | if err != nil { 77 | println(err.Error()) 78 | return "error" 79 | } 80 | fmt.Dump(msg) 81 | case models.CALLBACK_EVENT_SCAN: 82 | msg := models.EventScanCodePush{} 83 | err := event.ReadMessage(&msg) 84 | if err != nil { 85 | println(err.Error()) 86 | return "error" 87 | } 88 | } 89 | 90 | //return replyData 91 | return messages.NewText("ok") 92 | 93 | //media_id := "JRzcFCs0neDADadmOep2YOszEXI0ZFesCRP75VgIX7UgLzy7Uqk2YeYcwyHtOmAe" 94 | //return messages.NewImage(media_id, &power.HashMap{}) 95 | //return kernel.SUCCESS_EMPTY_RESPONSE 96 | 97 | }) 98 | if err != nil { 99 | panic(err) 100 | } 101 | 102 | err = helper.HttpResponseSend(rs, c.Writer) 103 | 104 | if err != nil { 105 | panic(err) 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module power-wechat-tutorial 2 | 3 | go 1.23 4 | 5 | replace github.com/ArtisanCloud/PowerWeChat/v3 => ../PowerWeChat 6 | 7 | replace github.com/ArtisanCloud/PowerLibs/v3 => ../PowerLibs 8 | 9 | //replace github.com/ArtisanCloud/PowerSocialite/v3 => ../PowerSocialite 10 | 11 | require ( 12 | github.com/ArtisanCloud/PowerLibs/v3 v3.2.6 13 | github.com/ArtisanCloud/PowerWeChat/v3 v3.2.45 14 | github.com/gin-contrib/cors v1.4.0 15 | github.com/gin-gonic/gin v1.9.1 16 | github.com/go-playground/assert/v2 v2.2.0 17 | github.com/golang-module/carbon v1.6.0 18 | github.com/jinzhu/configor v1.2.1 19 | github.com/swaggo/swag v1.16.3 20 | golang.org/x/text v0.18.0 21 | ) 22 | 23 | require ( 24 | github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 // indirect 25 | github.com/BurntSushi/toml v0.3.1 // indirect 26 | github.com/KyleBanks/depth v1.2.1 // indirect 27 | github.com/PuerkitoBio/purell v1.1.1 // indirect 28 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect 29 | github.com/bytedance/sonic v1.9.1 // indirect 30 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 31 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 32 | github.com/clbanning/mxj/v2 v2.7.0 // indirect 33 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 34 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 35 | github.com/gin-contrib/sse v0.1.0 // indirect 36 | github.com/go-openapi/jsonpointer v0.19.5 // indirect 37 | github.com/go-openapi/jsonreference v0.19.6 // indirect 38 | github.com/go-openapi/spec v0.20.4 // indirect 39 | github.com/go-openapi/swag v0.19.15 // indirect 40 | github.com/go-playground/locales v0.14.1 // indirect 41 | github.com/go-playground/universal-translator v0.18.1 // indirect 42 | github.com/go-playground/validator/v10 v10.14.1 // indirect 43 | github.com/gobuffalo/envy v1.7.0 // indirect 44 | github.com/gobuffalo/packd v0.3.0 // indirect 45 | github.com/gobuffalo/packr v1.30.1 // indirect 46 | github.com/goccy/go-json v0.10.2 // indirect 47 | github.com/joho/godotenv v1.3.0 // indirect 48 | github.com/josharian/intern v1.0.0 // indirect 49 | github.com/json-iterator/go v1.1.12 // indirect 50 | github.com/klauspost/cpuid/v2 v2.2.5 // indirect 51 | github.com/leodido/go-urn v1.2.4 // indirect 52 | github.com/mailru/easyjson v0.7.6 // indirect 53 | github.com/mattn/go-isatty v0.0.19 // indirect 54 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 55 | github.com/modern-go/reflect2 v1.0.2 // indirect 56 | github.com/patrickmn/go-cache v2.1.0+incompatible // indirect 57 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 58 | github.com/pkg/errors v0.9.1 // indirect 59 | github.com/redis/go-redis/v9 v9.1.0 // indirect 60 | github.com/rogpeppe/go-internal v1.8.0 // indirect 61 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 62 | github.com/ugorji/go/codec v1.2.11 // indirect 63 | go.uber.org/multierr v1.11.0 // indirect 64 | go.uber.org/zap v1.25.0 // indirect 65 | golang.org/x/arch v0.3.0 // indirect 66 | golang.org/x/crypto v0.27.0 // indirect 67 | golang.org/x/net v0.25.0 // indirect 68 | golang.org/x/sys v0.25.0 // indirect 69 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 70 | google.golang.org/protobuf v1.30.0 // indirect 71 | gopkg.in/yaml.v2 v2.4.0 // indirect 72 | gopkg.in/yaml.v3 v3.0.1 // indirect 73 | ) 74 | -------------------------------------------------------------------------------- /controllers/miniprogram/img.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "io/ioutil" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 本接口提供基于小程序的图片智能裁剪能力 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.aiCrop.html 13 | func APIImgAICropByURL(c *gin.Context) { 14 | 15 | // https://cdn.pixabay.com/photo/2015/04/23/22/00/tree-736885_1280.jpg 16 | url, exist := c.GetQuery("url") 17 | if !exist { 18 | panic("parameter url expected") 19 | } 20 | 21 | rs, err := services.MiniProgramApp.Image.AICrop(c.Request.Context(), url, nil) 22 | 23 | if err != nil { 24 | panic(err) 25 | } 26 | 27 | c.JSON(http.StatusOK, rs) 28 | } 29 | 30 | // 本接口提供基于小程序的图片智能裁剪能力 31 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.aiCrop.html 32 | func APIImgAICropByData(c *gin.Context) { 33 | var err error 34 | mediaPath := "./resource/tree.png" 35 | value, err := ioutil.ReadFile(mediaPath) 36 | 37 | rs, err := services.MiniProgramApp.Image.AICrop(c.Request.Context(), "", &power.HashMap{ 38 | "name": "tree.png", // 请确保文件名有准确的文件类型 39 | "value": value, 40 | }) 41 | 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | c.JSON(http.StatusOK, rs) 47 | } 48 | 49 | // 本接口提供基于小程序的条码/二维码识别的API 50 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.scanQRCode.html 51 | func APIImgScanQRCodeByURL(c *gin.Context) { 52 | url, exist := c.GetQuery("url") 53 | if !exist { 54 | panic("parameter url expected") 55 | } 56 | 57 | rs, err := services.MiniProgramApp.Image.ScanQRCode(c.Request.Context(), url, nil) 58 | 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | c.JSON(http.StatusOK, rs) 64 | } 65 | 66 | // 本接口提供基于小程序的条码/二维码识别的API 67 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.scanQRCode.html 68 | func APIImgScanQRCodeByData(c *gin.Context) { 69 | var err error 70 | mediaPath := "./resource/qrcode.png" 71 | value, err := ioutil.ReadFile(mediaPath) 72 | 73 | rs, err := services.MiniProgramApp.Image.ScanQRCode(c.Request.Context(), "", &power.HashMap{ 74 | "name": "qrcode.png", // 请确保文件名有准确的文件类型 75 | "value": value, 76 | }) 77 | 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | c.JSON(http.StatusOK, rs) 83 | } 84 | 85 | // 本接口提供基于小程序的图片高清化能力 86 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.superresolution.html 87 | func APIImgSuperResolutionByURL(c *gin.Context) { 88 | url, exist := c.GetQuery("url") 89 | if !exist { 90 | panic("parameter url expected") 91 | } 92 | 93 | rs, err := services.MiniProgramApp.Image.SuperResolution(c.Request.Context(), url, nil) 94 | 95 | if err != nil { 96 | panic(err) 97 | } 98 | 99 | c.JSON(http.StatusOK, rs) 100 | } 101 | 102 | // 本接口提供基于小程序的图片高清化能力 103 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/img/img.superresolution.html 104 | func APIImgSuperResolutionByData(c *gin.Context) { 105 | var err error 106 | mediaPath := "./resource/tree.png" 107 | value, err := ioutil.ReadFile(mediaPath) 108 | 109 | rs, err := services.MiniProgramApp.Image.SuperResolution(c.Request.Context(), "", &power.HashMap{ 110 | "name": "tree.png", // 请确保文件名有准确的文件类型 111 | "value": value, 112 | }) 113 | 114 | if err != nil { 115 | panic(err) 116 | } 117 | 118 | c.JSON(http.StatusOK, rs) 119 | } 120 | -------------------------------------------------------------------------------- /controllers/payment/refund/refund.go: -------------------------------------------------------------------------------- 1 | package refund 2 | 3 | import ( 4 | "context" 5 | request2 "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/partner/request" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | "net/http" 9 | "power-wechat-tutorial/services" 10 | "time" 11 | ) 12 | 13 | func APIMakeOrder(c *gin.Context) { 14 | options := &request2.RequestJSAPIPrepay{ 15 | Amount: &request2.JSAPIAmount{ 16 | Total: 1, 17 | Currency: "CNY", 18 | }, 19 | Attach: "自定义数据说明", 20 | Description: "Image形象店-深圳腾大-QQ公仔", 21 | OutTradeNo: "5519778939773395659222498002", // 这里是商户订单号,不能重复提交给微信 22 | Payer: &request2.JSAPIPayer{ 23 | SpOpenId: "o4QEk5Mf1Do7utS7-SF5Go30s8i4", // 用户的openid, 记得也是动态的。 24 | }, 25 | } 26 | 27 | // 如果需要覆盖掉全局的notify_url 28 | options.SetNotifyUrl("https://pay.xxx.com/wx/notify") 29 | 30 | // 下单 31 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 32 | defer cancel() 33 | response, err := services.PaymentApp.Partner.JSAPITransaction(ctx, options) 34 | 35 | if err != nil { 36 | log.Printf("error: %s", err) 37 | c.JSON(400, response) 38 | return 39 | } 40 | 41 | payConf, err := services.PaymentApp.JSSDK.BridgeConfig(response.PrepayID, true) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | c.JSON(200, payConf) 47 | } 48 | 49 | // APIMakeOrderNative 生成Native支付二维码让用户扫 50 | func APIMakeOrderNative(c *gin.Context) { 51 | options := &request2.RequestNativePrepay{ 52 | Amount: &request2.NativeAmount{ 53 | Total: 1, 54 | Currency: "CNY", 55 | }, 56 | Attach: "自定义数据说明", 57 | Description: "Image形象店-深圳腾大-QQ公仔", 58 | OutTradeNo: "55197789397733956592225981111", // 这里是商户订单号,不能重复提交给微信 59 | } 60 | 61 | response, err := services.PaymentApp.Partner.TransactionNative(c.Request.Context(), options) 62 | 63 | if err != nil { 64 | log.Printf("error: %s", err) 65 | c.JSON(400, response) 66 | return 67 | } 68 | 69 | c.JSON(200, response) 70 | } 71 | 72 | // APIMakeOrder App下单 73 | func APIMakeOrderApp(c *gin.Context) { 74 | options := &request2.RequestAppPrepay{ 75 | Amount: &request2.AppAmount{ 76 | Total: 1, 77 | Currency: "CNY", 78 | }, 79 | Attach: "自定义数据说明", 80 | Description: "Image形象店-深圳腾大-QQ公仔", 81 | OutTradeNo: "5519778939773395659222498001", // 这里是商户订单号,不能重复提交给微信 82 | } 83 | 84 | // 如果需要覆盖掉全局的notify_url 85 | //options.SetNotifyUrl("https://pay.xxx.com/wx/notify") 86 | 87 | // 下单 88 | response, err := services.PaymentApp.Partner.TransactionApp(c.Request.Context(), options) 89 | 90 | if err != nil { 91 | log.Printf("error: %s", err) 92 | c.JSON(400, response) 93 | return 94 | } 95 | 96 | payConf, err := services.PaymentApp.JSSDK.BridgeConfig(response.PrepayID, true) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | c.JSON(200, payConf) 102 | } 103 | 104 | func APIQueryRefundOrder(c *gin.Context) { 105 | 106 | traceNo := c.Query("traceNo") 107 | 108 | rs, err := services.PaymentApp.Refund.Query(c.Request.Context(), traceNo) 109 | if err != nil { 110 | panic(err) 111 | } 112 | c.JSON(http.StatusOK, rs) 113 | 114 | } 115 | 116 | func APICloseOrder(c *gin.Context) { 117 | traceNo := c.Query("traceNo") 118 | log.Printf("traceNo: %s", traceNo) 119 | 120 | rs, err := services.PaymentApp.Partner.Close(c.Request.Context(), traceNo) 121 | if err != nil { 122 | log.Println("出错了: ", err) 123 | c.String(400, err.Error()) 124 | return 125 | } 126 | c.JSON(http.StatusOK, rs) 127 | 128 | } 129 | -------------------------------------------------------------------------------- /controllers/payment/partner/payment.go: -------------------------------------------------------------------------------- 1 | package partner 2 | 3 | import ( 4 | "context" 5 | request2 "github.com/ArtisanCloud/PowerWeChat/v3/src/payment/partner/request" 6 | "github.com/gin-gonic/gin" 7 | "log" 8 | "net/http" 9 | "power-wechat-tutorial/services" 10 | "time" 11 | ) 12 | 13 | func APIMakeOrder(c *gin.Context) { 14 | options := &request2.RequestJSAPIPrepay{ 15 | Amount: &request2.JSAPIAmount{ 16 | Total: 1, 17 | Currency: "CNY", 18 | }, 19 | Attach: "自定义数据说明", 20 | Description: "Image形象店-深圳腾大-QQ公仔", 21 | OutTradeNo: "5519778939773395659222498002", // 这里是商户订单号,不能重复提交给微信 22 | Payer: &request2.JSAPIPayer{ 23 | SpOpenId: "o4QEk5Mf1Do7utS7-SF5Go30s8i4", // 用户的openid, 记得也是动态的。 24 | }, 25 | } 26 | 27 | // 如果需要覆盖掉全局的notify_url 28 | //options.SetNotifyUrl("https://pay.xxx.com/wx/notify") 29 | 30 | // 下单 31 | ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) 32 | defer cancel() 33 | response, err := services.PaymentApp.Partner.JSAPITransaction(ctx, options) 34 | 35 | if err != nil { 36 | log.Printf("error: %s", err) 37 | c.JSON(400, response) 38 | return 39 | } 40 | 41 | payConf, err := services.PaymentApp.JSSDK.BridgeConfig(response.PrepayID, true) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | c.JSON(200, payConf) 47 | } 48 | 49 | // APIMakeOrderNative 生成Native支付二维码让用户扫 50 | func APIMakeOrderNative(c *gin.Context) { 51 | options := &request2.RequestNativePrepay{ 52 | Amount: &request2.NativeAmount{ 53 | Total: 1, 54 | Currency: "CNY", 55 | }, 56 | Attach: "自定义数据说明", 57 | Description: "Image形象店-深圳腾大-QQ公仔", 58 | OutTradeNo: "55197789397733956592225981111", // 这里是商户订单号,不能重复提交给微信 59 | } 60 | 61 | response, err := services.PaymentApp.Partner.TransactionNative(c.Request.Context(), options) 62 | 63 | if err != nil { 64 | log.Printf("error: %s", err) 65 | c.JSON(400, response) 66 | return 67 | } 68 | 69 | c.JSON(200, response) 70 | } 71 | 72 | // APIMakeOrder App下单 73 | func APIMakeOrderApp(c *gin.Context) { 74 | options := &request2.RequestAppPrepay{ 75 | Amount: &request2.AppAmount{ 76 | Total: 1, 77 | Currency: "CNY", 78 | }, 79 | Attach: "自定义数据说明", 80 | Description: "Image形象店-深圳腾大-QQ公仔", 81 | OutTradeNo: "5519778939773395659222498001", // 这里是商户订单号,不能重复提交给微信 82 | } 83 | 84 | // 如果需要覆盖掉全局的notify_url 85 | //options.SetNotifyUrl("https://pay.xxx.com/wx/notify") 86 | 87 | // 下单 88 | response, err := services.PaymentApp.Partner.TransactionApp(c.Request.Context(), options) 89 | 90 | if err != nil { 91 | log.Printf("error: %s", err) 92 | c.JSON(400, response) 93 | return 94 | } 95 | 96 | payConf, err := services.PaymentApp.JSSDK.BridgeConfig(response.PrepayID, true) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | c.JSON(200, payConf) 102 | } 103 | 104 | func APIQueryOrder(c *gin.Context) { 105 | 106 | traceNo := c.Query("traceNo") 107 | 108 | rs, err := services.PaymentApp.Partner.QueryByOutTradeNumber(c.Request.Context(), traceNo) 109 | if err != nil { 110 | panic(err) 111 | } 112 | c.JSON(http.StatusOK, rs) 113 | 114 | } 115 | 116 | func APICloseOrder(c *gin.Context) { 117 | traceNo := c.Query("traceNo") 118 | log.Printf("traceNo: %s", traceNo) 119 | 120 | rs, err := services.PaymentApp.Partner.Close(c.Request.Context(), traceNo) 121 | if err != nil { 122 | log.Println("出错了: ", err) 123 | c.String(400, err.Error()) 124 | return 125 | } 126 | c.JSON(http.StatusOK, rs) 127 | 128 | } 129 | -------------------------------------------------------------------------------- /controllers/wecom/oa/schedule.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | "strconv" 9 | ) 10 | 11 | // 创建日程 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93648 13 | func APIScheduleAdd(c *gin.Context) { 14 | 15 | agentId := c.DefaultQuery("agentID", "1000014") 16 | agentID, _ := strconv.Atoi(agentId) 17 | 18 | schedule := &power.HashMap{ 19 | "organizer": "userid1", 20 | "start_time": 1571274600, 21 | "end_time": 1571320210, 22 | "attendees": []power.HashMap{ 23 | power.HashMap{ 24 | "userid": "userid2", 25 | }, 26 | }, 27 | "summary": "需求评审会议", 28 | "description": "2.0版本需求初步评审", 29 | "reminders": power.HashMap{ 30 | "is_remind": 1, 31 | "remind_before_event_secs": 3600, 32 | "is_repeat": 1, 33 | "repeat_type": 7, 34 | "repeat_until": 1606976813, 35 | "is_custom_repeat": 1, 36 | "repeat_interval": 1, 37 | "repeat_day_of_week": []int{3, 7}, 38 | "repeat_day_of_month": []int{10, 21}, 39 | "timezone": 8, 40 | }, 41 | "location": "广州国际媒体港10楼1005会议室", 42 | "cal_id": "wcjgewCwAAqeJcPI1d8Pwbjt7nttzAAA", 43 | } 44 | 45 | res, err := services.WeComApp.OASchedule.Add(c.Request.Context(), schedule, agentID) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | c.JSON(http.StatusOK, res) 52 | } 53 | 54 | // 更新日程 55 | // https://work.weixin.qq.com/api/doc/90000/90135/93648 56 | func APIScheduleUpdate(c *gin.Context) { 57 | 58 | schedule := &power.HashMap{ 59 | "organizer": "userid1", 60 | "schedule_id": "17c7d2bd9f20d652840f72f59e796AAA", 61 | "start_time": 1571274600, 62 | "end_time": 1571320210, 63 | "attendees": []power.HashMap{ 64 | { 65 | "userid": "userid2", 66 | }, 67 | }, 68 | "summary": "test_summary", 69 | "description": "test_description", 70 | "reminders": power.HashMap{ 71 | "is_remind": 1, 72 | "remind_before_event_secs": 3600, 73 | "is_repeat": 1, 74 | "repeat_type": 7, 75 | "repeat_until": 1606976813, 76 | "is_custom_repeat": 1, 77 | "repeat_interval": 1, 78 | "repeat_day_of_week": []int{3, 7}, 79 | "repeat_day_of_month": []int{10, 21}, 80 | "timezone": 8, 81 | }, 82 | "location": "test_place", 83 | } 84 | res, err := services.WeComApp.OASchedule.Update(c.Request.Context(), schedule) 85 | 86 | if err != nil { 87 | panic(err) 88 | } 89 | 90 | c.JSON(http.StatusOK, res) 91 | } 92 | 93 | // 获取日程详情 94 | // https://work.weixin.qq.com/api/doc/90000/90135/93648 95 | func APIScheduleGet(c *gin.Context) { 96 | scheduleIDList := []string{ 97 | c.DefaultQuery("scheduleID", "17c7d2bd9f20d652840f72f59e796AAA"), 98 | } 99 | res, err := services.WeComApp.OASchedule.Get(c.Request.Context(), scheduleIDList) 100 | 101 | if err != nil { 102 | panic(err) 103 | } 104 | 105 | c.JSON(http.StatusOK, res) 106 | } 107 | 108 | // 删除日程 109 | // https://work.weixin.qq.com/api/doc/90000/90135/93648 110 | func APIScheduleDel(c *gin.Context) { 111 | scheduleID := c.DefaultQuery("scheduleID", "17c7d2bd9f20d652840f72f59e796AAA") 112 | res, err := services.WeComApp.OASchedule.Del(c.Request.Context(), scheduleID) 113 | 114 | if err != nil { 115 | panic(err) 116 | } 117 | 118 | c.JSON(http.StatusOK, res) 119 | } 120 | -------------------------------------------------------------------------------- /controllers/official-account/comment.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "net/http" 6 | "power-wechat-tutorial/services" 7 | "strconv" 8 | ) 9 | 10 | // CommentOpen 打开已群发文章评论 11 | func CommentOpen(ctx *gin.Context) { 12 | msgID := ctx.Query("msgID") 13 | data, err := services.OfficialAccountApp.Comment.Open(ctx.Request.Context(), msgID, 0) 14 | if err != nil { 15 | ctx.String(http.StatusBadRequest, err.Error()) 16 | return 17 | } 18 | ctx.JSON(http.StatusOK, data) 19 | } 20 | 21 | // CommentClose 关闭已群发文章评论 22 | func CommentClose(ctx *gin.Context) { 23 | msgID := ctx.Query("msgID") 24 | data, err := services.OfficialAccountApp.Comment.Close(ctx.Request.Context(), msgID, 0) 25 | if err != nil { 26 | ctx.String(http.StatusBadRequest, err.Error()) 27 | return 28 | } 29 | ctx.JSON(http.StatusOK, data) 30 | } 31 | 32 | // CommentList 查看指定文章的评论数据 33 | func CommentList(ctx *gin.Context) { 34 | msgID := ctx.Query("msgID") 35 | data, err := services.OfficialAccountApp.Comment.List(ctx.Request.Context(), msgID, 0, 0, 100, 0) 36 | if err != nil { 37 | ctx.String(http.StatusBadRequest, err.Error()) 38 | return 39 | } 40 | ctx.JSON(http.StatusOK, data) 41 | } 42 | 43 | // CommentMarkElect 将评论标记精选 44 | func CommentMarkElect(ctx *gin.Context) { 45 | msgID := ctx.Query("msgID") 46 | commentIDStr := ctx.Query("commentID") 47 | commentID, _ := strconv.Atoi(commentIDStr) 48 | data, err := services.OfficialAccountApp.Comment.MarkElect(ctx.Request.Context(), msgID, 0, commentID) 49 | if err != nil { 50 | ctx.String(http.StatusBadRequest, err.Error()) 51 | return 52 | } 53 | ctx.JSON(http.StatusOK, data) 54 | } 55 | 56 | // CommentUnMarkElect 将评论取消精选 57 | func CommentUnMarkElect(ctx *gin.Context) { 58 | msgID := ctx.Query("msgID") 59 | commentIDStr := ctx.Query("commentID") 60 | commentID, _ := strconv.Atoi(commentIDStr) 61 | data, err := services.OfficialAccountApp.Comment.UnmarkElect(ctx.Request.Context(), msgID, 0, commentID) 62 | if err != nil { 63 | ctx.String(http.StatusBadRequest, err.Error()) 64 | return 65 | } 66 | ctx.JSON(http.StatusOK, data) 67 | } 68 | 69 | // CommentDelete 删除评论 70 | func CommentDelete(ctx *gin.Context) { 71 | msgID := ctx.Query("msgID") 72 | commentIDStr := ctx.Query("commentID") 73 | commentID, _ := strconv.Atoi(commentIDStr) 74 | data, err := services.OfficialAccountApp.Comment.Delete(ctx.Request.Context(), msgID, 0, commentID) 75 | if err != nil { 76 | ctx.String(http.StatusBadRequest, err.Error()) 77 | return 78 | } 79 | ctx.JSON(http.StatusOK, data) 80 | } 81 | 82 | // CommentReply 回复评论 83 | func CommentReply(ctx *gin.Context) { 84 | msgID := ctx.Query("msgID") 85 | commentIDStr := ctx.Query("commentID") 86 | content := ctx.Query("content") 87 | commentID, _ := strconv.Atoi(commentIDStr) 88 | data, err := services.OfficialAccountApp.Comment.Reply(ctx.Request.Context(), msgID, 0, commentID, content) 89 | if err != nil { 90 | ctx.String(http.StatusBadRequest, err.Error()) 91 | return 92 | } 93 | ctx.JSON(http.StatusOK, data) 94 | } 95 | 96 | // CommentDeleteReply 删除回复评论 97 | func CommentDeleteReply(ctx *gin.Context) { 98 | msgID := ctx.Query("msgID") 99 | commentIDStr := ctx.Query("commentID") 100 | commentID, _ := strconv.Atoi(commentIDStr) 101 | data, err := services.OfficialAccountApp.Comment.DeleteReply(ctx.Request.Context(), msgID, 0, commentID) 102 | if err != nil { 103 | ctx.String(http.StatusBadRequest, err.Error()) 104 | return 105 | } 106 | ctx.JSON(http.StatusOK, data) 107 | } 108 | -------------------------------------------------------------------------------- /controllers/official-account/menu.go: -------------------------------------------------------------------------------- 1 | package official_account 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/officialAccount/menu/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | func MenuList(ctx *gin.Context) { 11 | data, err := services.OfficialAccountApp.Menu.Get(ctx.Request.Context()) 12 | if err != nil { 13 | ctx.String(http.StatusBadRequest, err.Error()) 14 | return 15 | } 16 | ctx.JSON(http.StatusOK, data) 17 | } 18 | 19 | func MenuGet(ctx *gin.Context) { 20 | data, err := services.OfficialAccountApp.Menu.Get(ctx.Request.Context()) 21 | if err != nil { 22 | ctx.String(http.StatusBadRequest, err.Error()) 23 | return 24 | } 25 | ctx.JSON(http.StatusOK, data) 26 | } 27 | 28 | func MenuCurrent(ctx *gin.Context) { 29 | data, err := services.OfficialAccountApp.Menu.CurrentSelfMenu(ctx.Request.Context()) 30 | if err != nil { 31 | ctx.String(http.StatusBadRequest, err.Error()) 32 | return 33 | } 34 | ctx.JSON(http.StatusOK, data) 35 | } 36 | 37 | func MenuCreate(ctx *gin.Context) { 38 | data, err := services.OfficialAccountApp.Menu.Create(ctx.Request.Context(), []*request.Button{ 39 | { 40 | Type: "view", 41 | Name: "最新文章", 42 | URL: "https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5NDY5ODc5NA==&action=getalbum&album_id=2690448178684411909#wechat_redirect", 43 | }, 44 | //{ 45 | // Name: "知识分类", 46 | // SubButtons: []request.SubButton{ 47 | // { 48 | // Type: "click", 49 | // Name: "AI市场", 50 | // URL: "V1001_AI_MARKET", 51 | // }, 52 | // { 53 | // Type: "click", 54 | // Name: "AI技术", 55 | // Key: "V1001_AI_TECH", 56 | // }, 57 | // { 58 | // Type: "click", 59 | // Name: "AI运营", 60 | // Key: "V1001_AI_OPERATION", 61 | // }, 62 | // { 63 | // Type: "view", 64 | // Name: "AI方案", 65 | // Key: "V1001_AI_SOLUTION", 66 | // }, 67 | // }, 68 | //} 69 | }) 70 | if err != nil { 71 | ctx.String(http.StatusBadRequest, err.Error()) 72 | return 73 | } 74 | ctx.JSON(http.StatusOK, data) 75 | } 76 | 77 | func MenuCreateConditional(ctx *gin.Context) { 78 | data, err := services.OfficialAccountApp.Menu.CreateConditional(ctx.Request.Context(), []*request.Button{ 79 | { 80 | Type: "click", 81 | Name: "今日歌曲", 82 | Key: "V1001_TODAY_MUSIC", 83 | }, 84 | }, &request.RequestMatchRule{ 85 | Sex: "1", 86 | Country: "中国", 87 | Province: "广东", 88 | City: "广州", 89 | ClientPlatformType: "2", 90 | }) 91 | if err != nil { 92 | ctx.String(http.StatusBadRequest, err.Error()) 93 | return 94 | } 95 | ctx.JSON(http.StatusOK, data) 96 | } 97 | 98 | func MenuDelete(ctx *gin.Context) { 99 | data, err := services.OfficialAccountApp.Menu.Delete(ctx.Request.Context()) 100 | if err != nil { 101 | ctx.String(http.StatusBadRequest, err.Error()) 102 | return 103 | } 104 | ctx.JSON(http.StatusOK, data) 105 | } 106 | 107 | func MenuDeleteConditional(ctx *gin.Context) { 108 | data, err := services.OfficialAccountApp.Menu.DeleteConditional(ctx.Request.Context(), 1) 109 | if err != nil { 110 | ctx.String(http.StatusBadRequest, err.Error()) 111 | return 112 | } 113 | ctx.JSON(http.StatusOK, data) 114 | } 115 | 116 | func MenuMatch(ctx *gin.Context) { 117 | userID := ctx.Query("userID") 118 | data, err := services.OfficialAccountApp.Menu.TryMatch(ctx.Request.Context(), userID) 119 | if err != nil { 120 | ctx.String(http.StatusBadRequest, err.Error()) 121 | return 122 | } 123 | ctx.JSON(http.StatusOK, data) 124 | } 125 | -------------------------------------------------------------------------------- /routes/payment.go: -------------------------------------------------------------------------------- 1 | package routes 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "power-wechat-tutorial/controllers/payment" 6 | "power-wechat-tutorial/controllers/payment/merchant" 7 | "power-wechat-tutorial/controllers/payment/merchantService" 8 | "power-wechat-tutorial/controllers/payment/partner" 9 | "power-wechat-tutorial/controllers/payment/paymentScore" 10 | "power-wechat-tutorial/controllers/payment/redpack" 11 | "power-wechat-tutorial/controllers/payment/refund" 12 | "power-wechat-tutorial/controllers/payment/tax" 13 | "power-wechat-tutorial/controllers/payment/transfer" 14 | ) 15 | 16 | func InitPaymentAPIRoutes(r *gin.Engine) { 17 | 18 | r.Static("/wx/payment", "./templates") 19 | r.POST("/wx/notify", payment.CallbackWXNotify) 20 | r.POST("/v3/pay/transactions/out-trade-no/5519778939773395659222199111/close", payment.APIMockCloseOrderResponse) 21 | 22 | apiRouterPayment := r.Group("/payment") 23 | { 24 | // Handle the pay route 25 | apiRouterPayment.GET("/order/make", payment.APIMakeOrder) 26 | apiRouterPayment.GET("/order/make/native", payment.APIMakeOrderNative) 27 | apiRouterPayment.GET("/order/make/app", payment.APIMakeOrderApp) 28 | apiRouterPayment.GET("/order/query", payment.APIQueryOrder) 29 | apiRouterPayment.GET("/order/close", payment.APICloseOrder) 30 | apiRouterPayment.GET("/order/refund", payment.APIRefundOrder) 31 | apiRouterPayment.GET("/order/revertOrderByOutTradeNumber", payment.APIRevertOrderByOutTradeNumber) 32 | 33 | apiRouterPayment.GET("/order/refund/query", refund.APIQueryRefundOrder) 34 | 35 | // Handle the partner pay route 36 | apiRouterPayment.GET("/partner/make", partner.APIMakeOrder) 37 | apiRouterPayment.GET("/partner/make/native", partner.APIMakeOrderNative) 38 | apiRouterPayment.GET("/partner/make/app", partner.APIMakeOrderApp) 39 | apiRouterPayment.GET("/partner/query", partner.APIQueryOrder) 40 | apiRouterPayment.GET("/partner/close", partner.APICloseOrder) 41 | 42 | // Handle the bill route 43 | apiRouterPayment.GET("/bill/downloadURL", payment.APIBillDownloadURL) 44 | 45 | apiRouterPayment.GET("/merchant/uploadImg", merchant.APIUploadImg) 46 | apiRouterPayment.GET("/merchantService/complaints", merchantService.APIComplaints) 47 | 48 | // Handle payment route 49 | apiRouterPayment.GET("redpack/sendNormal", redpack.APISendNormal) 50 | apiRouterPayment.GET("redpack/info", redpack.APIQueryRedPack) 51 | apiRouterPayment.GET("work/sendworkwxredpack", redpack.APIWorkSendWXRedpack) 52 | apiRouterPayment.GET("transfer/toBalance", transfer.APIToTransfer) 53 | apiRouterPayment.GET("transfer/toBankCard", transfer.APIToBankCard) 54 | apiRouterPayment.GET("transfer/queryBalanceOrder", transfer.APIQueryBalanceOrder) 55 | apiRouterPayment.GET("transfer/batch/batchTransfer", transfer.APIBatchTransfer) 56 | apiRouterPayment.GET("transfer/batch/queryBatchOrder", transfer.APIQueryBatchOrder) 57 | apiRouterPayment.GET("transfer/batch/queryBatchOrderDetail", transfer.APIQueryBatchOrderDetail) 58 | 59 | // Handle security route 60 | apiRouterPayment.GET("security/getRSAPublicKey", payment.APIGetRSAPublicKey) 61 | apiRouterPayment.POST("security/decryptCertificate", payment.APIDecryptCertificate) 62 | apiRouterPayment.GET("security/getCertificates", payment.APIGetCertificates) 63 | 64 | apiRouterPayment.GET("tax/applyForCardTemplate", tax.APIApplyForCardTemplate) 65 | 66 | apiRouterPayment.GET("paymentScore/serviceOrder", paymentScore.APIServiceOrder) 67 | 68 | // Handle profitSharing route 69 | apiRouterPayment.GET("profitSharing/orders", payment.APIOrders) 70 | apiRouterPayment.GET("profitSharing/addReceiver", payment.APIAddReceiver) 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /controllers/wecom/external-contact/transfer.go: -------------------------------------------------------------------------------- 1 | package external_contact 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/externalContact/transfer/request" 5 | "github.com/gin-gonic/gin" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | ) 9 | 10 | // 分配在职成员的客户 11 | // https://work.weixin.qq.com/api/doc/90000/90135/92125 12 | func APIExternalContactTransferCustomer(c *gin.Context) { 13 | options := &request.RequestTransferCustomer{ 14 | HandoverUserID: c.DefaultQuery("handoverUserID", "walle"), 15 | TakeoverUserID: c.DefaultQuery("takeoverUserID", "matrix-x"), 16 | ExternalUserID: []string{c.DefaultQuery("externalUserID", "woAJ2GCAAAXtWyujaWJHDDGi0mACAAAA")}, 17 | TransferSuccessMsg: "您好,您的服务已升级,后续将由我的同事李四@腾讯接替我的工作,继续为您服务。", 18 | } 19 | 20 | res, err := services.WeComContactApp.ExternalContactTransfer.TransferCustomer(c.Request.Context(), options) 21 | 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | c.JSON(http.StatusOK, res) 27 | } 28 | 29 | // 查询客户接替状态 30 | // https://work.weixin.qq.com/api/doc/90000/90135/94088 31 | func APIExternalContactTransferResult(c *gin.Context) { 32 | 33 | options := &request.RequestTransferResult{ 34 | HandoverUserID: "walle", 35 | TakeoverUserID: "matrix-x", 36 | Cursor: c.DefaultQuery("cursor", "CURSOR"), 37 | } 38 | res, err := services.WeComContactApp.ExternalContactTransfer.TransferResult(c.Request.Context(), options) 39 | 40 | if err != nil { 41 | panic(err) 42 | } 43 | 44 | c.JSON(http.StatusOK, res) 45 | } 46 | 47 | // 获取待分配的离职成员列表 48 | // https://work.weixin.qq.com/api/doc/90000/90135/92125 49 | func APIExternalContactGetUnassignedList(c *gin.Context) { 50 | 51 | pageID := 1 52 | pageSize := 1000 53 | cursor := "" 54 | 55 | res, err := services.WeComContactApp.ExternalContactTransfer.GetUnassignedList(c.Request.Context(), pageID, cursor, pageSize) 56 | 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | c.JSON(http.StatusOK, res) 62 | } 63 | 64 | // 分配离职成员的客户 65 | // https://work.weixin.qq.com/api/doc/90000/90135/94081 66 | func APIExternalContactResignedTransferCustomer(c *gin.Context) { 67 | 68 | options := &request.RequestResignedTransferCustomer{ 69 | HandoverUserID: "walle", 70 | TakeoverUserID: "matrix-x", 71 | ExternalUserID: []string{"woAJ2GCAAAXtWyujaWJHDDGi0mACBBBB"}, 72 | } 73 | 74 | res, err := services.WeComContactApp.ExternalContactTransfer.ResignedTransferCustomer(c.Request.Context(), options) 75 | 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | c.JSON(http.StatusOK, res) 81 | } 82 | 83 | // 查询客户接替状态 84 | // https://work.weixin.qq.com/api/doc/90000/90135/94082 85 | func APIExternalContactResignedTransferResult(c *gin.Context) { 86 | options := &request.RequestResignedTransferResult{ 87 | HandoverUserID: "walle", 88 | TakeoverUserID: "matrix-x", 89 | Cursor: c.DefaultQuery("cursor", "CURSOR"), 90 | } 91 | 92 | res, err := services.WeComContactApp.ExternalContactTransfer.ResignedTransferResult(c.Request.Context(), options) 93 | 94 | if err != nil { 95 | panic(err) 96 | } 97 | 98 | c.JSON(http.StatusOK, res) 99 | } 100 | 101 | // 分配离职成员的客户群 102 | // https://work.weixin.qq.com/api/doc/90000/90135/92127 103 | func APIExternalContactGroupChatTransfer(c *gin.Context) { 104 | 105 | options := &request.RequestGroupChatTransfer{ 106 | ChatIDList: []string{"wrOgQhDgAAcwMTB7YmDkbeBsgT_AAAA", "wrOgQhDgAAMYQiS5ol9G7gK9JVQUAAAA"}, 107 | NewOwner: "walle", 108 | } 109 | 110 | res, err := services.WeComContactApp.ExternalContactTransfer.GroupChatTransfer(c.Request.Context(), options) 111 | 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | c.JSON(http.StatusOK, res) 117 | } 118 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs Code generated by swaggo/swag. DO NOT EDIT 2 | package docs 3 | 4 | import "github.com/swaggo/swag" 5 | 6 | const docTemplate = `{ 7 | "schemes": {{ marshal .Schemes }}, 8 | "swagger": "2.0", 9 | "info": { 10 | "description": "{{escape .Description}}", 11 | "title": "{{.Title}}", 12 | "termsOfService": "http://artisan-cloud.com/terms/", 13 | "contact": { 14 | "name": "ArtisanCloud Support", 15 | "url": "https://powerwechat.artisan-cloud.com/zh/start/qa.html", 16 | "email": "matrix-x@artisan-cloud.como" 17 | }, 18 | "license": { 19 | "name": "MIT 2.0", 20 | "url": "https://github.com/ArtisanCloud/PowerWeChat?tab=MIT-1-ov-file#readme" 21 | }, 22 | "version": "{{.Version}}" 23 | }, 24 | "host": "{{.Host}}", 25 | "basePath": "{{.BasePath}}", 26 | "paths": { 27 | "/clearQuota": { 28 | "get": { 29 | "description": "\nSDK产品接口的代码展示:\n` + "`" + `` + "`" + `` + "`" + `\n返回类型定义如下:\ntype ResponseOfficialAccount struct {\n\tResponseBase\n\n\tErrCode int ` + "`" + `json:\"errcode,omitempty\"` + "`" + `\n\tErrMsg string ` + "`" + `json:\"errmsg,omitempty\"` + "`" + `\n\n\tResultCode string ` + "`" + `json:\"resultcode,omitempty\"` + "`" + `\n\tResultMsg string ` + "`" + `json:\"resultmsg,omitempty\"` + "`" + `\n}\n\n具体使用接口方式:\nfunc ClearQuota(ctx *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.ClearQuota(ctx.Request.Context())\n\tif err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, data)\n}\n` + "`" + `` + "`" + `` + "`" + `", 30 | "tags": [ 31 | "OfficialAccount.base.ClearQuota" 32 | ], 33 | "summary": "公众号里清空api的调用quota:https://developers.weixin.qq.com/doc/offiaccount/openApi/clear_quota.html", 34 | "responses": {} 35 | } 36 | }, 37 | "/getCallbackIp": { 38 | "get": { 39 | "description": "\nSDK产品接口的代码展示:\n` + "`" + `` + "`" + `` + "`" + `\n返回类型定义如下:\ntype ResponseGetCallBackIP struct {\n\tresponse.ResponseOfficialAccount\n\n\tIPList []string ` + "`" + `json:\"ip_list\"` + "`" + `\n}\n\n具体使用接口方式:\nfunc GetCallbackIP(ctx *gin.Context) {\n\tdata, err := services.OfficialAccountApp.Base.GetCallbackIP(ctx.Request.Context())\n\tif err != nil {\n\t\tctx.String(http.StatusBadRequest, err.Error())\n\t\treturn\n\t}\n\tctx.JSON(http.StatusOK, data)\n}\n` + "`" + `` + "`" + `` + "`" + `", 40 | "tags": [ 41 | "OfficialAccount.base.GetCallbackIP" 42 | ], 43 | "summary": "获取公众号回调的IP地址:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_the_WeChat_server_IP_address.html", 44 | "responses": {} 45 | } 46 | } 47 | }, 48 | "securityDefinitions": { 49 | "BasicAuth": { 50 | "type": "basic" 51 | } 52 | }, 53 | "externalDocs": { 54 | "description": "OpenAPI", 55 | "url": "https://swagger.io/resources/open-api/" 56 | } 57 | }` 58 | 59 | // SwaggerInfo holds exported Swagger Info so clients can modify it 60 | var SwaggerInfo = &swag.Spec{ 61 | Version: "1.0.1", 62 | Host: "localhost:8080", 63 | BasePath: "/api/v1", 64 | Schemes: []string{}, 65 | Title: "PowerWechat API Docs", 66 | Description: "这是一个开源的使用教程,包含PowerWechat的大部分接口调试代码.", 67 | InfoInstanceName: "swagger", 68 | SwaggerTemplate: docTemplate, 69 | LeftDelim: "{{", 70 | RightDelim: "}}", 71 | } 72 | 73 | func init() { 74 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 75 | } 76 | -------------------------------------------------------------------------------- /controllers/wecom/oauth-controller.go: -------------------------------------------------------------------------------- 1 | package wecom 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "log" 6 | "net/http" 7 | "power-wechat-tutorial/services" 8 | 9 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | func WebAuthorizeUser(ctx *gin.Context) { 14 | 15 | // $callbackUrl 为授权回调地址 16 | callbackUrl := services.WeComApp.GetConfig().GetString("oauth.callbacks", "artisan-cloud.com") + "/callback/authorized/user" // 需设置可信域名 17 | services.WeComApp.OAuth.Provider.WithRedirectURL(callbackUrl) 18 | 19 | // 返回一个 redirect 实例 20 | redirectURL, _ := services.WeComApp.OAuth.Provider.GetAuthURL() 21 | 22 | log.Println("redirectURL: ", redirectURL) 23 | 24 | // 请在企业微信客户端打开链接 25 | //http.Redirect(ctx.Writer, ctx.Request, redirectURL, http.StatusFound) 26 | ctx.Redirect(http.StatusFound, redirectURL) 27 | } 28 | 29 | func WebAuthorizedUser(ctx *gin.Context) { 30 | 31 | code := ctx.Query("code") 32 | user, err := services.WeComApp.OAuth.Provider.Detailed().UserFromCode(code) 33 | //user, err := services.WeComApp.OAuth.Provider.ContactFromCode(code) 34 | if err != nil { 35 | ctx.String(http.StatusBadRequest, err.Error()) 36 | } 37 | 38 | rawData, err := user.GetRaw() 39 | fmt.Dump(rawData) 40 | fmt.Dump((*rawData)["id"]) 41 | fmt.Dump(user.Attributes["id"]) 42 | 43 | if err != nil { 44 | ctx.String(http.StatusBadRequest, err.Error()) 45 | } 46 | 47 | rs := &power.HashMap{ 48 | "deviceID": user.GetDeviceID(), 49 | "userID": user.GetID(), 50 | "openID": user.GetOpenID(), 51 | } 52 | 53 | //// 正常返回json 54 | ctx.JSON(http.StatusOK, rs) 55 | } 56 | 57 | func WebAuthorizedUserV2(ctx *gin.Context) { 58 | 59 | code := ctx.Query("code") 60 | user, err := services.WeComApp.OAuth.Provider.GetUserInfo(code) 61 | if err != nil { 62 | ctx.String(http.StatusBadRequest, err.Error()) 63 | return 64 | } 65 | if user.ErrCode != 0 { 66 | ctx.JSON(http.StatusBadRequest, user) 67 | return 68 | } 69 | 70 | accessToken, err := services.WeComApp.AccessToken.GetToken(ctx.Request.Context(), false) 71 | if err != nil { 72 | ctx.String(http.StatusBadRequest, err.Error()) 73 | return 74 | } 75 | if accessToken.ErrCode != 0 { 76 | ctx.JSON(http.StatusBadRequest, accessToken) 77 | return 78 | } 79 | 80 | userDetail, err := services.WeComApp.OAuth.Provider.WithApiAccessToken(accessToken.AccessToken).GetUserDetail(user.UserTicket) 81 | if err != nil { 82 | ctx.String(http.StatusBadRequest, err.Error()) 83 | return 84 | } 85 | 86 | if userDetail.ErrCode != 0 { 87 | ctx.JSON(http.StatusBadRequest, userDetail) 88 | return 89 | } 90 | 91 | //// 正常返回json 92 | ctx.JSON(http.StatusOK, userDetail) 93 | } 94 | 95 | func WebAuthorizeContact(ctx *gin.Context) { 96 | 97 | // $callbackUrl 为授权回调地址 98 | // 需设置可信域名 99 | callbackUrl := services.WeComApp.GetConfig().GetString("oauth.callbacks", "") + "/callback/authorized/contact" 100 | 101 | // 返回一个 redirect 实例 102 | services.WeComApp.OAuth.Provider.WithRedirectURL(callbackUrl) 103 | redirectURL, _ := services.WeComApp.OAuth.Provider.GetQrConnectURL() 104 | 105 | log.Println("redirectURL: ", redirectURL) 106 | 107 | // 直接跳转到企业微信授权 108 | http.Redirect(ctx.Writer, ctx.Request, redirectURL, http.StatusFound) 109 | 110 | } 111 | 112 | func WebAuthorizedContact(ctx *gin.Context) { 113 | 114 | code := ctx.Query("code") 115 | user, err := services.WeComApp.OAuth.Provider.Detailed().ContactFromCode(code) 116 | if err != nil { 117 | panic(err) 118 | } 119 | 120 | rs := &power.HashMap{ 121 | "openID": user.GetOpenID(), 122 | "userID": user.GetID(), 123 | "name": user.GetName(), 124 | "avatar": user.GetAvatar(), 125 | } 126 | 127 | //// 正常返回json 128 | ctx.JSON(http.StatusOK, rs) 129 | 130 | } 131 | -------------------------------------------------------------------------------- /controllers/open-platform/open-platform.go: -------------------------------------------------------------------------------- 1 | package open_platform 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerLibs/v3/object" 6 | "github.com/gin-gonic/gin" 7 | "power-wechat-tutorial/services" 8 | "strconv" 9 | ) 10 | 11 | func GetPreAuthorizationUrl(ctx *gin.Context) { 12 | appID := ctx.DefaultQuery("app_id", "wx3c7e1c9f9f1f9f9f") 13 | refreshToken := ctx.DefaultQuery("refresh_token", "wx3c7e1c9f9f1f9f9f") 14 | //services.OpenPlatformApp.GetMobilePreAuthorizationURL() 15 | account, _ := services.OpenPlatformApp.OfficialAccount(appID, refreshToken, nil) 16 | a := account.Account 17 | a.Create(ctx.Request.Context()) 18 | } 19 | 20 | // 获取PreAuthorizationCode,component token会自动缓存 21 | func APIOpenPlatformPreAuthCode(ctx *gin.Context) { 22 | // 如果之前在回调的时候,已经将获取的ComponentVerifyTicket通过SetTicket缓存后,则可以注销代码,跳过这个环节 23 | ticket := ctx.Query("ticket") 24 | err := services.OpenPlatformApp.VerifyTicket.SetTicket(ticket) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rs, err := services.OpenPlatformApp.Base.CreatePreAuthorizationCode(ctx.Request.Context()) 30 | if err != nil { 31 | panic(err) 32 | } 33 | fmt.Dump(rs) 34 | ctx.JSON(200, rs) 35 | } 36 | 37 | func GetFastRegistrationURL(ctx *gin.Context) { 38 | // 如果之前在回调的时候,已经将获取的ComponentVerifyTicket通过SetTicket缓存后,则可以注销代码,跳过这个环节 39 | //ticket, _ := services.OpenPlatformApp.VerifyTicket.GetTicket() 40 | //if ticket == "" { 41 | // ticket = ctx.Query("ticket") 42 | // err := services.OpenPlatformApp.VerifyTicket.SetTicket(ticket) 43 | // if err != nil { 44 | // panic(err) 45 | // } 46 | //} 47 | 48 | url, err := services.OpenPlatformApp.GetFastRegistrationURL(ctx.Request.Context(), "https://michael.debug.artisancloud.cn/wx/api/openplatform/callback", &object.StringMap{ 49 | "auth_type": "1", 50 | }) 51 | 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | ctx.JSON(200, url) 57 | } 58 | 59 | // HandleAuthorize Code换调用凭据信息 60 | func HandleAuthorize(ctx *gin.Context) { 61 | authCode := ctx.DefaultQuery("auth_code", "") 62 | res, err := services.OpenPlatformApp.Base.HandleAuthorize(ctx.Request.Context(), authCode) 63 | if err != nil { 64 | panic(err) 65 | } 66 | ctx.JSON(200, res) 67 | } 68 | 69 | // GetAuthorizer 获取授权方的帐号基本信息 70 | func GetAuthorizer(ctx *gin.Context) { 71 | appID := ctx.DefaultQuery("app_id", "wx3c7e1c9f9f1f9f9f") 72 | res, err := services.OpenPlatformApp.Base.GetAuthorizer(ctx.Request.Context(), appID) 73 | if err != nil { 74 | panic(err) 75 | } 76 | ctx.JSON(200, res) 77 | } 78 | 79 | // GetAuthorizerOption 获取授权方的选项设置信息 80 | func GetAuthorizerOption(ctx *gin.Context) { 81 | appID := ctx.DefaultQuery("app_id", "wx3c7e1c9f9f1f9f9f") 82 | name := ctx.DefaultQuery("name", "location_report") 83 | res, err := services.OpenPlatformApp.Base.GetAuthorizerOption(ctx.Request.Context(), appID, name) 84 | if err != nil { 85 | panic(err) 86 | } 87 | ctx.JSON(200, res) 88 | } 89 | 90 | // SetAuthorizerOption 设置授权方的选项信息 91 | func SetAuthorizerOption(ctx *gin.Context) { 92 | appID := ctx.DefaultQuery("app_id", "wx3c7e1c9f9f1f9f9f") 93 | name := ctx.DefaultQuery("name", "location_report") 94 | value := ctx.DefaultQuery("value", "1") 95 | res, err := services.OpenPlatformApp.Base.SetAuthorizerOption(ctx.Request.Context(), appID, name, value) 96 | if err != nil { 97 | panic(err) 98 | } 99 | ctx.JSON(200, res) 100 | } 101 | 102 | // GetAuthorizers 获取授权方的帐号列表 103 | func GetAuthorizers(ctx *gin.Context) { 104 | offset := ctx.DefaultQuery("offset", "0") 105 | count := ctx.DefaultQuery("count", "10") 106 | offsetInt, _ := strconv.Atoi(offset) 107 | countInt, _ := strconv.Atoi(count) 108 | res, err := services.OpenPlatformApp.Base.GetAuthorizers(ctx.Request.Context(), offsetInt, countInt) 109 | if err != nil { 110 | panic(err) 111 | } 112 | ctx.JSON(200, res) 113 | } 114 | -------------------------------------------------------------------------------- /config-example.yml: -------------------------------------------------------------------------------- 1 | # 微信支付配置文档: https://powerwechat.artisan-cloud.com/zh/payment/index.html#userconfig%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E%EF%BC%9A 2 | payment: 3 | appid: xxxxx # 公众号appid、小程序appid、企业微信corpid等 4 | mchid: 100000 # ArtisanCloud商户号 5 | certpath: certs/apiclient_cert.pem # 证书路径 6 | keypath: certs/apiclient_key.pem # 证书私钥路径 7 | serialno: 5XXX06F99FF64CF175XXXXXXX 8 | key: xxxxx 9 | # certificatekeypath: /xxx/ArtisanCloud/dev/wx_cert_artisan_cloud/certificates_key.pem 10 | wechatpayserial: 7DED6E61FEA3C847XXXXXXXXXXXXXXX 11 | # rsapublickeypath: /xxx/ArtisanCloud/dev/wx_cert_artisan_cloud/wx_rsa_public_key.pem 12 | mchapiv3key: xxxxx 13 | notifyurl: https://www.artisancloud.cn 14 | submchid: xxxxx 15 | subappid: xxxxx 16 | redisaddr: localhost:6379 17 | 18 | # 小程序配置文档: https://powerwechat.artisan-cloud.com/zh/mini-program/index.html 19 | miniprogram: 20 | appid: xxxxx 21 | secret: xxxxx 22 | redisaddr: localhost:6379 23 | messagetoken: xxxx 24 | messageaeskey: xxxx 25 | 26 | # 企业微信配置文档: https://powerwechat.artisan-cloud.com/zh/wecom/index.html 27 | wecom: 28 | corpid: ww454dfb9d6f6d432a 29 | 30 | # ----- powerwechat-docs-demo start ------ 31 | agent_id: 1000000 32 | secret: 33 | messagetoken: xxxxx 34 | messageaeskey: KIBOjioaf3fabKJ32d9zjioafF 35 | messagecallback: https://www.artisan-cloud.com/message/callback 36 | oauthcallback: https://www.artisan-cloud.com/oauth/callback 37 | # ----- powerwechat-docs-demo end ------ 38 | 39 | # 联系人配置 40 | contactsecret: 41 | contacttoken: xxxxx 42 | contactaeskey: KIBOjioaf3fabKJ32d9zjioafF 43 | contactcallback: https://www.artisan-cloud.com/api/wx/customer 44 | 45 | redisaddr: localhost:6379 46 | 47 | # 公众号配置文档: https://powerwechat.artisan-cloud.com/zh/official-account/index.html 48 | offiaccount: 49 | appid: xxxxx 50 | appsecret: KIBOjioaf3fabKJ32d9zjioafF 51 | redisaddr: localhost:6379 52 | messagetoken: xxxxx 53 | messageaeskey: 54 | 55 | # 开放平台配置 56 | openplatform: 57 | appid: xxxxxx # 开放平台第三方平台 APPID 58 | appsecret: xxxxxx # 开放平台第三方平台 Secret' 59 | messagetoken: xxxxxx # 开放平台第三方平台 Token' 60 | messageaeskey: xxxxxx # 开放平台第三方平台 AES Key 61 | redisaddr: localhost:6379 62 | 63 | 64 | robotchat: 65 | artbot: 66 | channel: stableDiffusion 67 | stablediffusion: 68 | token: MTA5OTI0MjgzOTI2NDcyNzA2MAkeykeykeykeykeykeykeykeykeykeykeykeykey 69 | baseurl: http://127.0.0.1:7861 70 | prefixuri: 71 | version: 72 | httpdebug: 73 | proxyurl: 74 | 75 | queue: 76 | driver: redis 77 | # 异步操作回调通知 78 | notifyurl: http://127.0.0.1:8888/api/v1/webhook/art-bot/queue/notify 79 | redis: 80 | addr: 127.0.0.1:6379 81 | clientname: ArtBot 82 | username: 83 | password: 84 | db: 1 85 | maxretries: 3 86 | log: 87 | driver: zap 88 | env: develop 89 | infolog: ./logs/artBot/info.log 90 | errorlog: ./logs/artBot/error.log 91 | httpdebug: true 92 | 93 | chatbot: 94 | channel: OPENAI 95 | chatgpt: 96 | openapikey: sk-r4KaVKEmWiogWiVSkeykeykeykeykeykeykeykeykeykeykeykeykeykey 97 | model: ada:ft-55hudong-2023-06-15-05-51-27 98 | organization: 55hudong 99 | httpdebug: true 100 | baseurl: https://chat.kxllen.com/v1 101 | apitype: 102 | apiversion: v1 103 | 104 | queue: 105 | driver: redis 106 | # 异步操作回调通知 107 | notifyurl: http://127.0.0.1:8888/api/v1/webhook/chat-bot/queue/notify 108 | redis: 109 | addr: 127.0.0.1:6379 110 | clientname: ChatBot 111 | username: 112 | password: 113 | db: 2 114 | maxretries: 3 115 | log: 116 | driver: zap 117 | env: develop 118 | infolog: ./logs/chatBot/info.log 119 | errorlog: ./logs/chatBot/error.log 120 | httpdebug: true 121 | 122 | -------------------------------------------------------------------------------- /controllers/miniprogram/industry.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerLibs/v3/fmt" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram/industry/miniDrama/request" 6 | "github.com/gin-gonic/gin" 7 | "power-wechat-tutorial/services" 8 | "time" 9 | ) 10 | 11 | func APIVideoMediaUploadByURL(ctx *gin.Context) { 12 | 13 | var params []*request.VideoMediaUploadByURLRequest 14 | 15 | params = append(params, &request.VideoMediaUploadByURLRequest{ 16 | MediaUrl: "xxxx.mp4", 17 | CoverUrl: "xxxx", 18 | MediaName: "xxxx", 19 | }) 20 | params = append(params, &request.VideoMediaUploadByURLRequest{ 21 | MediaUrl: "xxxx", 22 | CoverUrl: "xxxx", 23 | MediaName: "xxx", 24 | }) 25 | 26 | for _, param := range params { 27 | 28 | result, err := services.MiniProgramApp.MiniDramaVOD.VideoMediaUploadByURL(ctx, param) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | fmt.Dump(result) 35 | } 36 | 37 | } 38 | 39 | func APISearchMediaByTaskId(ctx *gin.Context) { 40 | 41 | taskIDs := []int64{111, 222, 333, 444, 555} 42 | 43 | for _, task := range taskIDs { 44 | 45 | result, err := services.MiniProgramApp.MiniDramaVOD.SearchMediaByTaskId(ctx, task) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | fmt.Dump(result) 52 | } 53 | 54 | } 55 | 56 | func APIGetMediaList(ctx *gin.Context) { 57 | 58 | result, err := services.MiniProgramApp.MiniDramaVOD.GetMediaList(ctx, &request.MediaListRequest{}) 59 | 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | fmt.Dump(result) 65 | 66 | } 67 | 68 | func APIGetMediaInfo(ctx *gin.Context) { 69 | MediaID := int64(111) 70 | 71 | result, err := services.MiniProgramApp.MiniDramaVOD.GetMediaInfo(ctx, MediaID) 72 | 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | fmt.Dump(result) 78 | 79 | } 80 | 81 | // 需要提审后才能调用该接口 82 | func APIGetMediaLink(ctx *gin.Context) { 83 | 84 | in := &request.GetMediaLinkRequest{ 85 | MediaId: 1111, 86 | T: time.Now().Unix() + 60*60*2, // 最大2小时过期 87 | Expr: 10, // 试看时常 88 | } 89 | 90 | result, err := services.MiniProgramApp.MiniDramaVOD.GetMediaLink(ctx, in) 91 | 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | fmt.Dump(result) 97 | } 98 | 99 | func APIDeleteMedia(ctx *gin.Context) { 100 | 101 | MediaIds := []int64{111, 222, 333} 102 | 103 | for _, MediaID := range MediaIds { 104 | 105 | result, err := services.MiniProgramApp.MiniDramaVOD.DeleteMedia(ctx, MediaID) 106 | 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | fmt.Dump(result) 112 | } 113 | 114 | } 115 | 116 | func UploadMediaMp(ctx *gin.Context) { 117 | result, err := services.MiniProgramApp.CustomerServiceMessage.UploadTempMedia(ctx, "image", "xxx.png", nil) 118 | 119 | if err != nil { 120 | 121 | panic(err) 122 | } 123 | 124 | fmt.Dump(result) 125 | } 126 | 127 | func APISubmitAudit(ctx *gin.Context) { 128 | 129 | result, err := services.MiniProgramApp.MiniDramaVOD.SubmitAudit(ctx, &request.SubmitAuditRequest{ 130 | Name: "xxxx", 131 | MediaCount: 1, 132 | MediaIdList: []int64{111, 222, 333, 444, 555}, 133 | Producer: "xxxxx", // 作者 134 | CoverMaterialId: "xxxxxxxx", // 封面临时素材id (三天的那个 这里要调用《小程序》的上传临时素材接口) 135 | RegistrationNumber: "xxxxxx", // 备案号 136 | }) 137 | 138 | if err != nil { 139 | panic(err) 140 | } 141 | 142 | fmt.Dump(result) 143 | 144 | } 145 | 146 | func APIListDramas(ctx *gin.Context) { 147 | result, err := services.MiniProgramApp.MiniDramaVOD.GetDramaList(ctx, &request.ListRequest{}) 148 | 149 | if err != nil { 150 | panic(err) 151 | } 152 | 153 | fmt.Dump(result) 154 | 155 | } 156 | 157 | func APIUploadByFile(ctx *gin.Context) { 158 | result, err := services.MiniProgramApp.MiniDramaVOD.VideoMediaUploadByFile(ctx, nil) 159 | 160 | if err != nil { 161 | panic(err) 162 | } 163 | 164 | fmt.Dump(result) 165 | 166 | } 167 | -------------------------------------------------------------------------------- /controllers/wecom/oa/living.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/living/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 创建预约直播 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93637 13 | func APILivingCreate(c *gin.Context) { 14 | 15 | options := &request.RequestLivingCreate{ 16 | AnchorUserID: "zhangsan", 17 | Theme: "theme", 18 | LivingStart: 1600000000, 19 | LivingDuration: 3600, 20 | Description: "test description", 21 | Type: 4, 22 | AgentID: 1000014, 23 | RemindTime: 60, 24 | ActivityCoverMediaID: "MEDIA_ID", 25 | ActivityShareMediaID: "MEDIA_ID", 26 | ActivityDetail: &power.HashMap{ 27 | "description": "活动描述,非活动类型的直播不用传", 28 | "image_list": []string{ 29 | "MEDIA_ID_1", 30 | "MEDIA_ID_2", 31 | }, 32 | }, 33 | } 34 | res, err := services.WeComApp.OALiving.Create(c.Request.Context(), options) 35 | 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | c.JSON(http.StatusOK, res) 41 | } 42 | 43 | func APILivingModify(c *gin.Context) { 44 | 45 | options := &request.RequestLivingModify{ 46 | LivingID: c.DefaultQuery("livingID", "XXXXXXXXX"), 47 | Theme: "theme", 48 | LivingStart: 1600100000, 49 | LivingDuration: 3600, 50 | Description: "test description", 51 | Type: 1, 52 | RemindTime: 60, 53 | } 54 | res, err := services.WeComApp.OALiving.Modify(c.Request.Context(), options) 55 | 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | c.JSON(http.StatusOK, res) 61 | } 62 | 63 | func APILivingCancel(c *gin.Context) { 64 | livingID := c.DefaultQuery("livingID", "XXXXXXXXX") 65 | res, err := services.WeComApp.OALiving.Cancel(c.Request.Context(), livingID) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, res) 72 | } 73 | 74 | func APILivingDeleteReplayData(c *gin.Context) { 75 | livingID := c.DefaultQuery("livingID", "XXXXXXXXX") 76 | res, err := services.WeComApp.OALiving.DeleteReplayData(c.Request.Context(), livingID) 77 | 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | c.JSON(http.StatusOK, res) 83 | } 84 | 85 | func APILivingGetLivingCode(c *gin.Context) { 86 | livingID := c.DefaultQuery("livingID", "XXXXXXXXX") 87 | openID := c.DefaultQuery("openID", "XXXXXXXXX") 88 | res, err := services.WeComApp.OALiving.GetLivingCode(c.Request.Context(), livingID, openID) 89 | 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | c.JSON(http.StatusOK, res) 95 | } 96 | 97 | func APILivingGetUserAllLivingID(c *gin.Context) { 98 | userID := c.DefaultQuery("userID", "XXXXXXXXX") 99 | cursor := "CURSOR" 100 | limit := 20 101 | res, err := services.WeComApp.OALiving.GetUserAllLivingID(c.Request.Context(), userID, cursor, limit) 102 | 103 | if err != nil { 104 | panic(err) 105 | } 106 | 107 | c.JSON(http.StatusOK, res) 108 | } 109 | 110 | func APILivingGetLivingInfo(c *gin.Context) { 111 | livingID := c.DefaultQuery("livingID", "XXXXXXXXX") 112 | res, err := services.WeComApp.OALiving.GetLivingInfo(c.Request.Context(), livingID) 113 | 114 | if err != nil { 115 | panic(err) 116 | } 117 | 118 | c.JSON(http.StatusOK, res) 119 | } 120 | 121 | func APILivingGetWatchStat(c *gin.Context) { 122 | livingID := c.DefaultQuery("livingID", "XXXXXXXXX") 123 | nextKey := c.DefaultQuery("nextKey", "XXXXXXXXX") 124 | 125 | res, err := services.WeComApp.OALiving.GetWatchStat(c.Request.Context(), livingID, nextKey) 126 | 127 | if err != nil { 128 | panic(err) 129 | } 130 | 131 | c.JSON(http.StatusOK, res) 132 | } 133 | 134 | func APILivingGetLivingShareInfo(c *gin.Context) { 135 | wwShareCode := c.DefaultQuery("wwShareCode", "XXXXXXXXX") 136 | 137 | res, err := services.WeComApp.OALiving.GetLivingShareInfo(c.Request.Context(), wwShareCode) 138 | 139 | if err != nil { 140 | panic(err) 141 | } 142 | 143 | c.JSON(http.StatusOK, res) 144 | } 145 | -------------------------------------------------------------------------------- /controllers/wecom/oa/meetingroom.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/meetingroom/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 添加会议室 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93619 13 | func APIMeetingRoomAdd(c *gin.Context) { 14 | 15 | options := &request.RequestMeetingRoomAdd{ 16 | Name: "18F-会议室", 17 | Capacity: 10, 18 | City: "深圳", 19 | Building: "腾讯大厦", 20 | Floor: "18F", 21 | Equipment: []int{1, 2, 3}, 22 | Coordinate: &power.StringMap{ 23 | "latitude": "22.540503", 24 | "longitude": "113.934528", 25 | }, 26 | } 27 | 28 | res, err := services.WeComApp.OAMeetingRoom.Add(c.Request.Context(), options) 29 | 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | c.JSON(http.StatusOK, res) 35 | } 36 | 37 | // 查询会议室 38 | // https://work.weixin.qq.com/api/doc/90000/90135/93619 39 | func APIMeetingRoomList(c *gin.Context) { 40 | 41 | options := &request.RequestMeetingRoomList{ 42 | City: "深圳", 43 | Building: "腾讯大厦", 44 | Floor: "18F", 45 | Equipment: []int{1, 2, 3}, 46 | } 47 | 48 | res, err := services.WeComApp.OAMeetingRoom.List(c.Request.Context(), options) 49 | 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | c.JSON(http.StatusOK, res) 55 | } 56 | 57 | // 编辑会议室 58 | // https://work.weixin.qq.com/api/doc/90000/90135/93619 59 | func APIMeetingRoomEdit(c *gin.Context) { 60 | 61 | options := &request.RequestMeetingRoomEdit{ 62 | MeetingRoomID: 2, 63 | Name: "18F-会议室", 64 | Capacity: 10, 65 | City: "深圳", 66 | Building: "腾讯大厦", 67 | Floor: "18F", 68 | Equipment: []int{1, 2, 3}, 69 | Coordinate: &power.StringMap{ 70 | "latitude": "22.540503", 71 | "longitude": "113.934528", 72 | }, 73 | } 74 | 75 | res, err := services.WeComApp.OAMeetingRoom.Edit(c.Request.Context(), options) 76 | 77 | if err != nil { 78 | panic(err) 79 | } 80 | 81 | c.JSON(http.StatusOK, res) 82 | } 83 | 84 | // 删除会议室 85 | // https://work.weixin.qq.com/api/doc/90000/90135/93619 86 | func APIMeetingRoomDel(c *gin.Context) { 87 | options := &request.RequestMeetingRoomDel{ 88 | MeetingRoomID: 1, 89 | } 90 | res, err := services.WeComApp.OAMeetingRoom.Del(c.Request.Context(), options) 91 | 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | c.JSON(http.StatusOK, res) 97 | } 98 | 99 | // 查询会议室的预定信息 100 | // https://work.weixin.qq.com/api/doc/90000/90135/93620#%E6%9F%A5%E8%AF%A2%E4%BC%9A%E8%AE%AE%E5%AE%A4%E7%9A%84%E9%A2%84%E5%AE%9A%E4%BF%A1%E6%81%AF 101 | func APIMeetingRoomGetBookingInfo(c *gin.Context) { 102 | 103 | options := &request.RequestMeetingRoomGetBookingInfo{ 104 | MeetingroomID: 1, 105 | StartTime: 1593532800, 106 | EndTime: 1593619200, 107 | City: "深圳", 108 | Building: "腾讯大厦", 109 | Floor: "18F", 110 | } 111 | res, err := services.WeComApp.OAMeetingRoom.GetBookingInfo(c.Request.Context(), options) 112 | 113 | if err != nil { 114 | panic(err) 115 | } 116 | 117 | c.JSON(http.StatusOK, res) 118 | } 119 | 120 | // 预定会议室 121 | // https://work.weixin.qq.com/api/doc/90000/90135/93620 122 | func APIMeetingRoomBook(c *gin.Context) { 123 | 124 | options := &request.RequestMeetingRoomBook{ 125 | MeetingRoomID: 1, 126 | Subject: "周会", 127 | StartTime: 1593532800, 128 | EndTime: 1593619200, 129 | Booker: "zhangsan", 130 | Attendees: []string{"lisi", "wangwu"}, 131 | } 132 | 133 | res, err := services.WeComApp.OAMeetingRoom.Book(c.Request.Context(), options) 134 | 135 | if err != nil { 136 | panic(err) 137 | } 138 | 139 | c.JSON(http.StatusOK, res) 140 | } 141 | 142 | func APIMeetingRoomCancelBook(c *gin.Context) { 143 | 144 | options := &request.RequestMeetingRoomCancelBook{ 145 | MeetingID: c.DefaultQuery("meetingID", "mt42b34949gsaseb6e027c123cbafAAA"), 146 | KeepSchedule: 1, 147 | } 148 | 149 | res, err := services.WeComApp.OAMeetingRoom.CancelBook(c.Request.Context(), options) 150 | 151 | if err != nil { 152 | panic(err) 153 | } 154 | 155 | c.JSON(http.StatusOK, res) 156 | } 157 | -------------------------------------------------------------------------------- /controllers/wecom/oa/checkin.go: -------------------------------------------------------------------------------- 1 | package oa 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/work/oa/request" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 获取企业所有打卡规则 12 | // https://work.weixin.qq.com/api/doc/90000/90135/93384 13 | func APICheckinGetCorpCheckinOption(c *gin.Context) { 14 | 15 | res, err := services.WeComApp.OA.GetCorpCheckInOption(c.Request.Context()) 16 | 17 | if err != nil { 18 | panic(err) 19 | } 20 | 21 | c.JSON(http.StatusOK, res) 22 | } 23 | 24 | // 获取员工打卡规则 25 | // https://work.weixin.qq.com/api/doc/90000/90135/90263 26 | func APICheckinGetCheckinOption(c *gin.Context) { 27 | 28 | datetime := 1511971200 29 | userIDList := []string{c.DefaultQuery("userID", "matrix-x")} 30 | 31 | res, err := services.WeComApp.OA.GetCheckInOption(c.Request.Context(), datetime, userIDList) 32 | 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | c.JSON(http.StatusOK, res) 38 | } 39 | 40 | // 获取打卡记录数据 41 | // https://work.weixin.qq.com/api/doc/90000/90135/90262 42 | func APICheckinGetCheckinData(c *gin.Context) { 43 | 44 | userIDList := []string{c.DefaultQuery("userID", "matrix-x")} 45 | 46 | res, err := services.WeComApp.OA.GetCheckinData(c.Request.Context(), 3, 1492617600, 1492790400, userIDList) 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | c.JSON(http.StatusOK, res) 53 | } 54 | 55 | // 获取打卡日报数据 56 | // https://work.weixin.qq.com/api/doc/90000/90135/93374 57 | func APICheckinGetCheckinDayData(c *gin.Context) { 58 | userIDList := []string{c.DefaultQuery("userID", "matrix-x")} 59 | 60 | res, err := services.WeComApp.OA.GetCheckinDayData(c.Request.Context(), 1599062400, 1599062400, userIDList) 61 | 62 | if err != nil { 63 | panic(err) 64 | } 65 | 66 | c.JSON(http.StatusOK, res) 67 | } 68 | 69 | // 获取打卡月报数据 70 | // https://work.weixin.qq.com/api/doc/90000/90135/93387 71 | func APICheckinGetCheckinMonthData(c *gin.Context) { 72 | userIDList := []string{c.DefaultQuery("userID", "matrix-x")} 73 | 74 | res, err := services.WeComApp.OA.GetCheckInMonthData(c.Request.Context(), 1599062400, 1599408000, userIDList) 75 | 76 | if err != nil { 77 | panic(err) 78 | } 79 | 80 | c.JSON(http.StatusOK, res) 81 | } 82 | 83 | // 获取打卡人员排班信息 84 | // https://work.weixin.qq.com/api/doc/90000/90135/93380 85 | func APICheckinGetCheckinSchedulist(c *gin.Context) { 86 | userIDList := []string{c.DefaultQuery("userID", "matrix-x")} 87 | 88 | res, err := services.WeComApp.OA.GetCheckInScheduleList(c.Request.Context(), 1492617600, 1492790400, userIDList) 89 | 90 | if err != nil { 91 | panic(err) 92 | } 93 | 94 | c.JSON(http.StatusOK, res) 95 | } 96 | 97 | // 为打卡人员排班 98 | // https://work.weixin.qq.com/api/doc/90000/90135/93385 99 | func APICheckinSetCheckinSchedulist(c *gin.Context) { 100 | 101 | options := &request.RequestCheckInSetScheduleList{ 102 | GroupID: 226, 103 | Items: []*power.HashMap{ 104 | &power.HashMap{ 105 | "userid": "james", 106 | "day": 5, 107 | "schedule_id": 234, 108 | }, 109 | }, 110 | YearMonth: 202012, 111 | } 112 | 113 | res, err := services.WeComApp.OA.SetCheckInScheduleList(c.Request.Context(), options) 114 | 115 | if err != nil { 116 | panic(err) 117 | } 118 | 119 | c.JSON(http.StatusOK, res) 120 | 121 | } 122 | 123 | // 录入打卡人员人脸信息 124 | // https://work.weixin.qq.com/api/doc/90000/90135/93378 125 | func APICheckinAddCheckinUserFace(c *gin.Context) { 126 | userID := c.DefaultQuery("userID", "matrix-x") 127 | userFace := c.DefaultQuery("userFace", "PLACE_HOLDER") 128 | 129 | res, err := services.WeComApp.OA.AddCheckInUserFace(c.Request.Context(), userID, userFace) 130 | 131 | if err != nil { 132 | panic(err) 133 | } 134 | 135 | c.JSON(http.StatusOK, res) 136 | } 137 | 138 | // 创建打卡规则 139 | // https://developer.work.weixin.qq.com/document/path/98041#创建打卡规则 140 | func APICheckinAddCheckinOption(c *gin.Context) { 141 | //userID := c.DefaultQuery("userID", "matrix-x") 142 | //userFace := c.DefaultQuery("userFace", "PLACE_HOLDER") 143 | 144 | res, err := services.WeComApp.OA.AddCheckinOption(c.Request.Context(), nil) 145 | 146 | if err != nil { 147 | panic(err) 148 | } 149 | 150 | c.JSON(http.StatusOK, res) 151 | } 152 | -------------------------------------------------------------------------------- /controllers/miniprogram/subscribe-message.go: -------------------------------------------------------------------------------- 1 | package miniprogram 2 | 3 | import ( 4 | "github.com/ArtisanCloud/PowerWeChat/v3/src/basicService/subscribeMessage/request" 5 | "github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/power" 6 | "github.com/gin-gonic/gin" 7 | "net/http" 8 | "power-wechat-tutorial/services" 9 | ) 10 | 11 | // 组合模板并添加至帐号下的个人模板库 12 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html 13 | func APISubscribeMessageAddTemplate(c *gin.Context) { 14 | tID, exist := c.GetQuery("tid") 15 | if !exist { 16 | panic("parameter tid expected") 17 | } 18 | 19 | rs, err := services.MiniProgramApp.SubscribeMessage.AddTemplate(c.Request.Context(), tID, []int{1, 2}, "测试数据") 20 | 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | c.JSON(http.StatusOK, rs) 26 | } 27 | 28 | // 删除帐号下的个人模板 29 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html 30 | func APISubscribeMessageDeleteTemplate(c *gin.Context) { 31 | priTmplID, exist := c.GetQuery("priTmplID") 32 | if !exist { 33 | panic("parameter tid expected") 34 | } 35 | 36 | rs, err := services.MiniProgramApp.SubscribeMessage.DeleteTemplate(c.Request.Context(), priTmplID) 37 | 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | c.JSON(http.StatusOK, rs) 43 | } 44 | 45 | // 获取小程序账号的类目 46 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getCategory.html 47 | func APISubscribeMessageGetCategory(c *gin.Context) { 48 | rs, err := services.MiniProgramApp.SubscribeMessage.GetCategory(c.Request.Context()) 49 | 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | c.JSON(http.StatusOK, rs) 55 | } 56 | 57 | // 获取模板标题下的关键词列表 58 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getPubTemplateKeyWordsById.html 59 | func APISubscribeMessageGetPubTemplateKeyWordsByID(c *gin.Context) { 60 | tID, exist := c.GetQuery("tid") 61 | if !exist { 62 | panic("parameter tid expected") 63 | } 64 | 65 | rs, err := services.MiniProgramApp.SubscribeMessage.GetPubTemplateKeyWordsByID(c.Request.Context(), tID) 66 | 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | c.JSON(http.StatusOK, rs) 72 | } 73 | 74 | // 获取帐号所属类目下的公共模板标题 75 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getPubTemplateTitleList.html 76 | func APISubscribeMessageGetPubTemplateTitleList(c *gin.Context) { 77 | rs, err := services.MiniProgramApp.SubscribeMessage.GetPubTemplateTitleList(c.Request.Context(), "676", 0, 10) 78 | 79 | if err != nil { 80 | panic(err) 81 | } 82 | 83 | c.JSON(http.StatusOK, rs) 84 | } 85 | 86 | // 获取当前帐号下的个人模板列表 87 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html 88 | func APISubscribeMessageGetTemplateList(c *gin.Context) { 89 | rs, err := services.MiniProgramApp.SubscribeMessage.GetTemplateList(c.Request.Context()) 90 | 91 | if err != nil { 92 | panic(err) 93 | } 94 | 95 | c.JSON(http.StatusOK, rs) 96 | } 97 | 98 | // 发送订阅消息 99 | // https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html 100 | func APISubscribeMessageSend(c *gin.Context) { 101 | 102 | toUser, exist := c.GetQuery("openid") 103 | if !exist { 104 | panic("parameter open id expected") 105 | } 106 | 107 | templateID := c.DefaultQuery("templateID", "Y1471771tIQyEogSHjqCgD1P7iy52N_JYH-q0Sw7EvQ") 108 | 109 | page := "page/index/index" 110 | miniprogramState := "developer" 111 | lang := "zh_CN" 112 | 113 | data := &power.HashMap{ 114 | "thing1": power.StringMap{ 115 | "value": "新年活动", 116 | }, 117 | "time2": power.StringMap{ 118 | "value": "2023-01-01", 119 | }, 120 | } 121 | 122 | rs, err := services.MiniProgramApp.SubscribeMessage.Send(c.Request.Context(), 123 | &request.RequestSubscribeMessageSend{ 124 | ToUser: toUser, 125 | TemplateID: templateID, 126 | Page: page, 127 | MiniProgramState: miniprogramState, 128 | Lang: lang, 129 | Data: data, 130 | }) 131 | 132 | if err != nil { 133 | panic(err) 134 | } 135 | 136 | c.JSON(http.StatusOK, rs) 137 | } 138 | --------------------------------------------------------------------------------