Add WeChat Pay local skills

This commit is contained in:
2026-05-15 03:35:30 +08:00
parent 2eded08bc7
commit 6672867c6f
535 changed files with 114971 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# 开发必要参数说明
普通商户模式接入微信支付 APIv3 前需要准备开发必要参数mchid、appid、商户API证书、微信支付公钥、APIv3密钥等
## APPID 详解与绑定
### APPID 类型
APPID 是微信生态中应用的唯一标识,格式都是 `wx` + 一串字符(如 `wxd678efh567hg6787`),根据注册平台不同分为三种类型:
| APPID 类型 | 注册平台 | 用途 |
|-----------|---------|------|
| 公众号 AppID | 公众平台mp.weixin.qq.com | 服务号/订阅号,用于公众号内网页场景 |
| 小程序 AppID | 公众平台mp.weixin.qq.com | 微信小程序场景 |
| 移动应用 AppID | 开放平台open.weixin.qq.com | 原生 APPiOS/Android/鸿蒙)场景 |
> **三种 APPID 格式相同但不能混用**。拿小程序 AppID 做 JSAPI 支付会报错,拿公众号 AppID 做 APP 支付也不行。
### 为什么需要绑定 APPID
微信支付的所有支付方式都要求商户号与 APPID 建立绑定关系,未绑定时下单接口会报错。
### 如何查询 APPID
| APPID 类型 | 查询路径 |
|-----------|---------|
| 服务号/公众号 | 登录公众平台 → 设置与开发 → 开发接口管理 → 基本配置 → 开发者ID(AppID) |
| 小程序 | 登录公众平台 → 开发与服务 → 开发管理 → 开发设置 → AppID(小程序ID) |
| 移动应用 | 登录开放平台 → 管理中心 → 移动应用 → 查看 → 详情页面 → APPID |
### 如何绑定
**第一步:在商户平台发起绑定申请**
登录商户平台 → 产品中心 → APPID授权管理 → +关联AppID → 新增授权 → 填写 APPID → 提交
- 主体一致:直接填写 APPID 提交
- 主体不一致:还需填写 APPID 认证主体,并勾选《微信支付联合营运承诺函》
**第二步:在对应平台确认授权**
| APPID 类型 | 确认路径 |
|-----------|---------|
| 服务号/公众号 | 登录公众平台 → 微信支付 → 商户号管理 → 待关联商户号 → 确认 |
| 小程序 | 登录公众平台 → 微信支付 → 商户号管理 → 待关联商户号 → 确认 |
| 移动应用 | 登录开放平台 → 移动应用 → 详情 → 能力专区 → 微信支付 → 查询详情 → 待关联商户号 → 确认 |
> 委托第三方创建的小程序,需先设置邮箱密码后登录 PC 端确认。
**第三步:查看绑定结果**
登录商户平台 → 产品中心 → APPID账号管理 → 我关联的APPID账号
### 绑定限制
| 限制项 | 说明 |
|-------|------|
| 数量上限 | 一个商户号最多关联 50 个 APPID |
| 解绑 | 绑定后不支持解绑,每条关系相互独立 |
| 跨主体 | 需补充 APPID 主体信息 |
| 特殊费率 | 享有特殊行业费率的商户号提交后有额外审核1-3个工作日 |
| 费率一致性 | APPID 已绑定其他商户号时,新商户号的费率需与已绑定的一致 |
| 风控 | 商户号或 APPID 存在风险时(资料不全、有未处理处罚等),可能增加审核或被驳回 |
### APPID 相关常见报错
| 报错信息 | 原因 | 处理方式 |
|---------|------|---------|
| `appid and mchid not match` | 下单时传入的 appid 与商户号未建立绑定关系 | 按上述流程绑定 |
| `appid is invalid` | appid 格式不对,或使用了错误类型的 appid | 检查是否用了正确类型的 APPID如 JSAPI 需要公众号 AppID不能用小程序 AppID |
| JSAPI 支付报权限错误 | 商户号绑定的是小程序 APPID但用 JSAPI 调起 | JSAPI 需要绑定服务号 APPID |
## 参数与代码示例的对应关系
示例代码中构造函数所需的参数与上述开发必要参数的对应:
```
mchid → 商户号
certificateSerialNo → 商户API证书序列号
privateKeyFilePath → 商户API证书私钥文件路径apiclient_key.pem
wechatPayPublicKeyId → 微信支付公钥ID
wechatPayPublicKeyFilePath → 微信支付公钥文件路径wxp_pub.pem
```

View File

@@ -0,0 +1,203 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &DirectApiv3JsapiPrepayRequest{
Appid: wxpay_utility.String("wxd678efh567hg6787"),
Mchid: wxpay_utility.String("1230000109"),
Description: wxpay_utility.String("Image形象店-深圳腾大-QQ公仔"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
TimeExpire: wxpay_utility.Time(time.Now()),
Attach: wxpay_utility.String("自定义数据说明"),
NotifyUrl: wxpay_utility.String(" https://www.weixin.qq.com/wxpay/pay.php"),
GoodsTag: wxpay_utility.String("WXG"),
SupportFapiao: wxpay_utility.Bool(false),
Amount: &CommonAmountInfo{
Total: wxpay_utility.Int64(100),
Currency: wxpay_utility.String("CNY"),
},
Payer: &JsapiReqPayerInfo{
Openid: wxpay_utility.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
},
Detail: &CouponInfo{
CostPrice: wxpay_utility.Int64(608800),
InvoiceId: wxpay_utility.String("微信123"),
GoodsDetail: []GoodsDetail{GoodsDetail{
MerchantGoodsId: wxpay_utility.String("1246464644"),
WechatpayGoodsId: wxpay_utility.String("1001"),
GoodsName: wxpay_utility.String("iPhoneX 256G"),
Quantity: wxpay_utility.Int64(1),
UnitPrice: wxpay_utility.Int64(528800),
}},
},
SceneInfo: &CommonSceneInfo{
PayerClientIp: wxpay_utility.String("14.23.150.211"),
DeviceId: wxpay_utility.String("013467007045764"),
StoreInfo: &StoreInfo{
Id: wxpay_utility.String("0001"),
Name: wxpay_utility.String("腾讯大厦分店"),
AreaCode: wxpay_utility.String("440305"),
Address: wxpay_utility.String("广东省深圳市南山区科技中一道10000号"),
},
},
SettleInfo: &SettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
}
response, err := JsapiPrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func JsapiPrepay(config *wxpay_utility.MchConfig, request *DirectApiv3JsapiPrepayRequest) (response *DirectApiv3JsapiPrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/pay/transactions/jsapi"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3JsapiPrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type DirectApiv3JsapiPrepayRequest struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
Description *string `json:"description,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
Attach *string `json:"attach,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SupportFapiao *bool `json:"support_fapiao,omitempty"`
Amount *CommonAmountInfo `json:"amount,omitempty"`
Payer *JsapiReqPayerInfo `json:"payer,omitempty"`
Detail *CouponInfo `json:"detail,omitempty"`
SceneInfo *CommonSceneInfo `json:"scene_info,omitempty"`
SettleInfo *SettleInfo `json:"settle_info,omitempty"`
}
type DirectApiv3JsapiPrepayResponse struct {
PrepayId *string `json:"prepay_id,omitempty"`
}
type CommonAmountInfo struct {
Total *int64 `json:"total,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type JsapiReqPayerInfo struct {
Openid *string `json:"openid,omitempty"`
}
type CouponInfo struct {
CostPrice *int64 `json:"cost_price,omitempty"`
InvoiceId *string `json:"invoice_id,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type CommonSceneInfo struct {
PayerClientIp *string `json:"payer_client_ip,omitempty"`
DeviceId *string `json:"device_id,omitempty"`
StoreInfo *StoreInfo `json:"store_info,omitempty"`
}
type SettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
}
type StoreInfo struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
AreaCode *string `json:"area_code,omitempty"`
Address *string `json:"address,omitempty"`
}

View File

@@ -0,0 +1,177 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
)
// 申请交易账单API
//
// 关键注意:
// 1. 次日10点后拉取API仅支持3个月内单日账单更早的需在商户平台下载。
// 2. 返回的是下载链接download_url需二次请求下载gzip压缩CSV
// 3. 账单金额单位为"元"与下单API的"分"不同,对账时注意转换。
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &GetTradeBillRequest{
BillDate: wxpay_utility.String("2019-06-11"),
BillType: BILLTYPE_ALL.Ptr(),
TarType: TARTYPE_GZIP.Ptr(),
}
response, err := GetTradeBill(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// GetTradeBill 申请交易账单API
func GetTradeBill(config *wxpay_utility.MchConfig, request *GetTradeBillRequest) (response *QueryBillEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/bill/tradebill"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
query := reqUrl.Query()
if request.BillDate != nil {
query.Add("bill_date", *request.BillDate)
}
if request.BillType != nil {
query.Add("bill_type", fmt.Sprintf("%v", *request.BillType))
}
if request.TarType != nil {
query.Add("tar_type", fmt.Sprintf("%v", *request.TarType))
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &QueryBillEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type GetTradeBillRequest struct {
BillDate *string `json:"bill_date,omitempty"`
BillType *BillType `json:"bill_type,omitempty"`
TarType *TarType `json:"tar_type,omitempty"`
}
func (o *GetTradeBillRequest) MarshalJSON() ([]byte, error) {
type Alias GetTradeBillRequest
a := &struct {
BillDate *string `json:"bill_date,omitempty"`
BillType *BillType `json:"bill_type,omitempty"`
TarType *TarType `json:"tar_type,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
BillDate: nil,
BillType: nil,
TarType: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type QueryBillEntity struct {
HashType *HashType `json:"hash_type,omitempty"`
HashValue *string `json:"hash_value,omitempty"`
DownloadUrl *string `json:"download_url,omitempty"`
}
type BillType string
func (e BillType) Ptr() *BillType {
return &e
}
const (
BILLTYPE_ALL BillType = "ALL"
BILLTYPE_SUCCESS BillType = "SUCCESS"
BILLTYPE_REFUND BillType = "REFUND"
)
type TarType string
func (e TarType) Ptr() *TarType {
return &e
}
const (
TARTYPE_GZIP TarType = "GZIP"
)
type HashType string
func (e HashType) Ptr() *HashType {
return &e
}
const (
HASHTYPE_SHA1 HashType = "SHA1"
)

View File

@@ -0,0 +1,171 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &GetFundFlowBillRequest{
BillDate: wxpay_utility.String("2019-06-11"),
AccountType: FUNDFLOWBILLACCOUNTTYPE_BASIC.Ptr(),
TarType: TARTYPE_GZIP.Ptr(),
}
response, err := GetFundFlowBill(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// GetFundFlowBill 申请资金账单API
func GetFundFlowBill(config *wxpay_utility.MchConfig, request *GetFundFlowBillRequest) (response *QueryBillEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/bill/fundflowbill"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
query := reqUrl.Query()
if request.BillDate != nil {
query.Add("bill_date", *request.BillDate)
}
if request.AccountType != nil {
query.Add("account_type", fmt.Sprintf("%v", *request.AccountType))
}
if request.TarType != nil {
query.Add("tar_type", fmt.Sprintf("%v", *request.TarType))
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &QueryBillEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type GetFundFlowBillRequest struct {
BillDate *string `json:"bill_date,omitempty"`
AccountType *FundFlowBillAccountType `json:"account_type,omitempty"`
TarType *TarType `json:"tar_type,omitempty"`
}
func (o *GetFundFlowBillRequest) MarshalJSON() ([]byte, error) {
type Alias GetFundFlowBillRequest
a := &struct {
BillDate *string `json:"bill_date,omitempty"`
AccountType *FundFlowBillAccountType `json:"account_type,omitempty"`
TarType *TarType `json:"tar_type,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
BillDate: nil,
AccountType: nil,
TarType: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type QueryBillEntity struct {
HashType *HashType `json:"hash_type,omitempty"`
HashValue *string `json:"hash_value,omitempty"`
DownloadUrl *string `json:"download_url,omitempty"`
}
type FundFlowBillAccountType string
func (e FundFlowBillAccountType) Ptr() *FundFlowBillAccountType {
return &e
}
const (
FUNDFLOWBILLACCOUNTTYPE_BASIC FundFlowBillAccountType = "BASIC"
FUNDFLOWBILLACCOUNTTYPE_OPERATION FundFlowBillAccountType = "OPERATION"
FUNDFLOWBILLACCOUNTTYPE_FEES FundFlowBillAccountType = "FEES"
)
type TarType string
func (e TarType) Ptr() *TarType {
return &e
}
const (
TARTYPE_GZIP TarType = "GZIP"
)
type HashType string
func (e HashType) Ptr() *HashType {
return &e
}
const (
HASHTYPE_SHA1 HashType = "SHA1"
)

View File

@@ -0,0 +1,71 @@
package wxpay_utility
import (
"bytes"
"io"
"net/http"
)
const Host = "https://api.mch.weixin.qq.com"
// SendGet 发送 GET 请求并返回已验签的应答 Body
func SendGet(config *MchConfig, uri string) ([]byte, error) {
return sendRequest(config, "GET", uri, nil)
}
// SendPost 发送 POST 请求并返回已验签的应答 Body
func SendPost(config *MchConfig, uri string, reqBody []byte) ([]byte, error) {
return sendRequest(config, "POST", uri, reqBody)
}
func sendRequest(config *MchConfig, method string, uri string, reqBody []byte) ([]byte, error) {
var bodyReader io.Reader
if reqBody != nil {
bodyReader = bytes.NewReader(reqBody)
}
httpRequest, err := http.NewRequest(method, Host+uri, bodyReader)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := BuildAuthorization(config.MchId(), config.CertificateSerialNo(),
config.PrivateKey(), method, uri, reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
if reqBody != nil {
httpRequest.Header.Set("Content-Type", "application/json")
}
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
err = ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
return respBody, nil
}
return nil, NewApiException(httpResponse.StatusCode, httpResponse.Header, respBody)
}

View File

@@ -0,0 +1,539 @@
package wxpay_utility
import (
"bytes"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"hash"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/tjfoc/gmsm/sm3"
)
type MchConfig struct {
mchId string
certificateSerialNo string
privateKeyFilePath string
wechatPayPublicKeyId string
wechatPayPublicKeyFilePath string
privateKey *rsa.PrivateKey
wechatPayPublicKey *rsa.PublicKey
}
func (c *MchConfig) MchId() string {
return c.mchId
}
func (c *MchConfig) CertificateSerialNo() string {
return c.certificateSerialNo
}
func (c *MchConfig) PrivateKey() *rsa.PrivateKey {
return c.privateKey
}
func (c *MchConfig) WechatPayPublicKeyId() string {
return c.wechatPayPublicKeyId
}
func (c *MchConfig) WechatPayPublicKey() *rsa.PublicKey {
return c.wechatPayPublicKey
}
func CreateMchConfig(
mchId string,
certificateSerialNo string,
privateKeyFilePath string,
wechatPayPublicKeyId string,
wechatPayPublicKeyFilePath string,
) (*MchConfig, error) {
mchConfig := &MchConfig{
mchId: mchId,
certificateSerialNo: certificateSerialNo,
privateKeyFilePath: privateKeyFilePath,
wechatPayPublicKeyId: wechatPayPublicKeyId,
wechatPayPublicKeyFilePath: wechatPayPublicKeyFilePath,
}
privateKey, err := LoadPrivateKeyWithPath(mchConfig.privateKeyFilePath)
if err != nil {
return nil, err
}
mchConfig.privateKey = privateKey
wechatPayPublicKey, err := LoadPublicKeyWithPath(mchConfig.wechatPayPublicKeyFilePath)
if err != nil {
return nil, err
}
mchConfig.wechatPayPublicKey = wechatPayPublicKey
return mchConfig, nil
}
func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
block, _ := pem.Decode([]byte(privateKeyStr))
if block == nil {
return nil, fmt.Errorf("decode private key err")
}
if block.Type != "PRIVATE KEY" {
return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse private key err:%s", err.Error())
}
privateKey, ok := key.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("not a RSA private key")
}
return privateKey, nil
}
func LoadPublicKey(publicKeyStr string) (publicKey *rsa.PublicKey, err error) {
block, _ := pem.Decode([]byte(publicKeyStr))
if block == nil {
return nil, errors.New("decode public key error")
}
if block.Type != "PUBLIC KEY" {
return nil, fmt.Errorf("the kind of PEM should be PUBLIC KEY")
}
key, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parse public key err:%s", err.Error())
}
publicKey, ok := key.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("%s is not rsa public key", publicKeyStr)
}
return publicKey, nil
}
func LoadPrivateKeyWithPath(path string) (privateKey *rsa.PrivateKey, err error) {
privateKeyBytes, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read private pem file err:%s", err.Error())
}
return LoadPrivateKey(string(privateKeyBytes))
}
func LoadPublicKeyWithPath(path string) (publicKey *rsa.PublicKey, err error) {
publicKeyBytes, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read certificate pem file err:%s", err.Error())
}
return LoadPublicKey(string(publicKeyBytes))
}
func EncryptOAEPWithPublicKey(message string, publicKey *rsa.PublicKey) (ciphertext string, err error) {
if publicKey == nil {
return "", fmt.Errorf("you should input *rsa.PublicKey")
}
ciphertextByte, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, publicKey, []byte(message), nil)
if err != nil {
return "", fmt.Errorf("encrypt message with public key err:%s", err.Error())
}
ciphertext = base64.StdEncoding.EncodeToString(ciphertextByte)
return ciphertext, nil
}
func DecryptAES256GCM(aesKey, associatedData, nonce, ciphertext string) (plaintext string, err error) {
decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
if err != nil {
return "", err
}
c, err := aes.NewCipher([]byte(aesKey))
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return "", err
}
dataBytes, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
if err != nil {
return "", err
}
return string(dataBytes), nil
}
func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
if privateKey == nil {
return "", fmt.Errorf("private key should not be nil")
}
h := crypto.Hash.New(crypto.SHA256)
_, err = h.Write([]byte(source))
if err != nil {
return "", nil
}
hashed := h.Sum(nil)
signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(signatureByte), nil
}
func VerifySHA256WithRSA(source string, signature string, publicKey *rsa.PublicKey) error {
if publicKey == nil {
return fmt.Errorf("public key should not be nil")
}
sigBytes, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
return fmt.Errorf("verify failed: signature is not base64 encoded")
}
hashed := sha256.Sum256([]byte(source))
err = rsa.VerifyPKCS1v15(publicKey, crypto.SHA256, hashed[:], sigBytes)
if err != nil {
return fmt.Errorf("verify signature with public key error:%s", err.Error())
}
return nil
}
func GenerateNonce() (string, error) {
const (
NonceSymbols = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
NonceLength = 32
)
bytes := make([]byte, NonceLength)
_, err := rand.Read(bytes)
if err != nil {
return "", err
}
symbolsByteLength := byte(len(NonceSymbols))
for i, b := range bytes {
bytes[i] = NonceSymbols[b%symbolsByteLength]
}
return string(bytes), nil
}
func BuildAuthorization(
mchid string,
certificateSerialNo string,
privateKey *rsa.PrivateKey,
method string,
canonicalURL string,
body []byte,
) (string, error) {
const (
SignatureMessageFormat = "%s\n%s\n%d\n%s\n%s\n"
HeaderAuthorizationFormat = "WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
)
nonce, err := GenerateNonce()
if err != nil {
return "", err
}
timestamp := time.Now().Unix()
message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
signature, err := SignSHA256WithRSA(message, privateKey)
if err != nil {
return "", err
}
authorization := fmt.Sprintf(
HeaderAuthorizationFormat,
mchid, nonce, timestamp, certificateSerialNo, signature,
)
return authorization, nil
}
func ExtractResponseBody(response *http.Response) ([]byte, error) {
if response.Body == nil {
return nil, nil
}
body, err := io.ReadAll(response.Body)
if err != nil {
return nil, fmt.Errorf("read response body err:[%s]", err.Error())
}
response.Body = io.NopCloser(bytes.NewBuffer(body))
return body, nil
}
const (
WechatPayTimestamp = "Wechatpay-Timestamp"
WechatPayNonce = "Wechatpay-Nonce"
WechatPaySignature = "Wechatpay-Signature"
WechatPaySerial = "Wechatpay-Serial"
RequestID = "Request-Id"
)
func validateWechatPaySignature(
wechatpayPublicKeyId string,
wechatpayPublicKey *rsa.PublicKey,
headers *http.Header,
body []byte,
) error {
timestampStr := headers.Get(WechatPayTimestamp)
serialNo := headers.Get(WechatPaySerial)
signature := headers.Get(WechatPaySignature)
nonce := headers.Get(WechatPayNonce)
timestamp, err := strconv.ParseInt(timestampStr, 10, 64)
if err != nil {
return fmt.Errorf("invalid timestamp: %w", err)
}
if time.Now().Sub(time.Unix(timestamp, 0)) > 5*time.Minute {
return fmt.Errorf("timestamp expired: %d", timestamp)
}
if serialNo != wechatpayPublicKeyId {
return fmt.Errorf(
"serial-no mismatch: got %s, expected %s",
serialNo,
wechatpayPublicKeyId,
)
}
message := fmt.Sprintf("%s\n%s\n%s\n", timestampStr, nonce, body)
if err := VerifySHA256WithRSA(message, signature, wechatpayPublicKey); err != nil {
return fmt.Errorf("invalid signature: %v", err)
}
return nil
}
func ValidateResponse(
wechatpayPublicKeyId string,
wechatpayPublicKey *rsa.PublicKey,
headers *http.Header,
body []byte,
) error {
if err := validateWechatPaySignature(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
return fmt.Errorf("validate response err: %w, RequestID: %s", err, headers.Get(RequestID))
}
return nil
}
func validateNotification(
wechatpayPublicKeyId string,
wechatpayPublicKey *rsa.PublicKey,
headers *http.Header,
body []byte,
) error {
if err := validateWechatPaySignature(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
return fmt.Errorf("validate notification err: %w", err)
}
return nil
}
type Resource struct {
Algorithm string `json:"algorithm"`
Ciphertext string `json:"ciphertext"`
AssociatedData string `json:"associated_data"`
Nonce string `json:"nonce"`
OriginalType string `json:"original_type"`
}
type Notification struct {
ID string `json:"id"`
CreateTime *time.Time `json:"create_time"`
EventType string `json:"event_type"`
ResourceType string `json:"resource_type"`
Resource *Resource `json:"resource"`
Summary string `json:"summary"`
Plaintext string
}
func (c *Notification) validate() error {
if c.Resource == nil {
return errors.New("resource is nil")
}
if c.Resource.Algorithm != "AEAD_AES_256_GCM" {
return fmt.Errorf("unsupported algorithm: %s", c.Resource.Algorithm)
}
if c.Resource.Ciphertext == "" {
return errors.New("ciphertext is empty")
}
if c.Resource.AssociatedData == "" {
return errors.New("associated_data is empty")
}
if c.Resource.Nonce == "" {
return errors.New("nonce is empty")
}
if c.Resource.OriginalType == "" {
return fmt.Errorf("original_type is empty")
}
return nil
}
func (c *Notification) decrypt(apiv3Key string) error {
if err := c.validate(); err != nil {
return fmt.Errorf("notification format err: %w", err)
}
plaintext, err := DecryptAES256GCM(
apiv3Key,
c.Resource.AssociatedData,
c.Resource.Nonce,
c.Resource.Ciphertext,
)
if err != nil {
return fmt.Errorf("notification decrypt err: %w", err)
}
c.Plaintext = plaintext
return nil
}
func ParseNotification(
wechatpayPublicKeyId string,
wechatpayPublicKey *rsa.PublicKey,
apiv3Key string,
headers *http.Header,
body []byte,
) (*Notification, error) {
if err := validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body); err != nil {
return nil, err
}
notification := &Notification{}
if err := json.Unmarshal(body, notification); err != nil {
return nil, fmt.Errorf("parse notification err: %w", err)
}
if err := notification.decrypt(apiv3Key); err != nil {
return nil, fmt.Errorf("notification decrypt err: %w", err)
}
return notification, nil
}
type ApiException struct {
statusCode int
header http.Header
body []byte
errorCode string
errorMessage string
}
func (c *ApiException) Error() string {
buf := bytes.NewBuffer(nil)
buf.WriteString(fmt.Sprintf("api error:[StatusCode: %d, Body: %s", c.statusCode, string(c.body)))
if len(c.header) > 0 {
buf.WriteString(" Header: ")
for key, value := range c.header {
buf.WriteString(fmt.Sprintf("\n - %v=%v", key, value))
}
buf.WriteString("\n")
}
buf.WriteString("]")
return buf.String()
}
func (c *ApiException) StatusCode() int {
return c.statusCode
}
func (c *ApiException) Header() http.Header {
return c.header
}
func (c *ApiException) Body() []byte {
return c.body
}
func (c *ApiException) ErrorCode() string {
return c.errorCode
}
func (c *ApiException) ErrorMessage() string {
return c.errorMessage
}
func NewApiException(statusCode int, header http.Header, body []byte) error {
ret := &ApiException{
statusCode: statusCode,
header: header,
body: body,
}
bodyObject := map[string]interface{}{}
if err := json.Unmarshal(body, &bodyObject); err == nil {
if val, ok := bodyObject["code"]; ok {
ret.errorCode = val.(string)
}
if val, ok := bodyObject["message"]; ok {
ret.errorMessage = val.(string)
}
}
return ret
}
func Time(t time.Time) *time.Time {
return &t
}
func String(s string) *string {
return &s
}
func Bytes(b []byte) *[]byte {
return &b
}
func Bool(b bool) *bool {
return &b
}
func Float64(f float64) *float64 {
return &f
}
func Float32(f float32) *float32 {
return &f
}
func Int64(i int64) *int64 {
return &i
}
func Int32(i int32) *int32 {
return &i
}
func generateHashFromStream(reader io.Reader, hashFunc func() hash.Hash, algorithmName string) (string, error) {
hash := hashFunc()
if _, err := io.Copy(hash, reader); err != nil {
return "", fmt.Errorf("failed to read stream for %s: %w", algorithmName, err)
}
return fmt.Sprintf("%x", hash.Sum(nil)), nil
}
func GenerateSHA256FromStream(reader io.Reader) (string, error) {
return generateHashFromStream(reader, sha256.New, "SHA256")
}
func GenerateSHA1FromStream(reader io.Reader) (string, error) {
return generateHashFromStream(reader, sha1.New, "SHA1")
}
func GenerateSM3FromStream(reader io.Reader) (string, error) {
h := sm3.New()
if _, err := io.Copy(h, reader); err != nil {
return "", fmt.Errorf("failed to read stream for SM3: %w", err)
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}

View File

@@ -0,0 +1,191 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionApiv3AppPrepayRequest{
CombineAppid: wxpay_utility.String("wxd678efh567hg6787"),
CombineOutTradeNo: wxpay_utility.String("20150806125345"),
CombineMchid: wxpay_utility.String("1900000109"),
SceneInfo: &UnionSceneInfo{
DeviceId: wxpay_utility.String("POS1:1"),
PayerClientIp: wxpay_utility.String("14.17.22.32"),
},
SubOrders: []UnionSubOrder{
UnionSubOrder{
Mchid: wxpay_utility.String("1230000109"),
OutTradeNo: wxpay_utility.String("20150806125346"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("深圳分店"),
Description: wxpay_utility.String("腾讯充值中心-QQ会员充值"),
Detail: wxpay_utility.String("买单费用"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
UnionSubOrder{
Mchid: wxpay_utility.String("1230000119"),
OutTradeNo: wxpay_utility.String("20150806125347"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("广州分店"),
Description: wxpay_utility.String("腾讯充值中心-微信充值"),
Detail: wxpay_utility.String("买单费用"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
},
CombinePayerInfo: &UnionAppPayerInfo{
Openid: wxpay_utility.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
},
TimeExpire: wxpay_utility.Time(time.Now()),
NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
}
response, err := UnionAppPrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnionAppPrepay(config *wxpay_utility.MchConfig, request *UnionApiv3AppPrepayRequest) (response *UnionApiv3AppPrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/combine-transactions/app"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &UnionApiv3AppPrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionApiv3AppPrepayRequest struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
CombineMchid *string `json:"combine_mchid,omitempty"`
SceneInfo *UnionSceneInfo `json:"scene_info,omitempty"`
SubOrders []UnionSubOrder `json:"sub_orders,omitempty"`
CombinePayerInfo *UnionAppPayerInfo `json:"combine_payer_info,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
TradeScenario *string `json:"trade_scenario,omitempty"`
}
type UnionApiv3AppPrepayResponse struct {
PrepayId *string `json:"prepay_id,omitempty"`
}
type UnionSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
PayerClientIp *string `json:"payer_client_ip,omitempty"`
}
type UnionSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Amount *UnionAmountInfo `json:"amount,omitempty"`
Attach *string `json:"attach,omitempty"`
Description *string `json:"description,omitempty"`
Detail *string `json:"detail,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SettleInfo *UnionSettleInfo `json:"settle_info,omitempty"`
}
type UnionAppPayerInfo struct {
Openid *string `json:"openid,omitempty"`
}
type UnionAmountInfo struct {
TotalAmount *int64 `json:"total_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type UnionSettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}

View File

@@ -0,0 +1,128 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionCloseRequest{
CombineOutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
CombineAppid: wxpay_utility.String("wxd678efh567hg6787"),
SubOrders: []UnionCloseSubOrder{UnionCloseSubOrder{
Mchid: wxpay_utility.String("1900000109"),
OutTradeNo: wxpay_utility.String("20150806125346"),
}},
}
err = UnionClose(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Println("请求成功")
}
func UnionClose(config *wxpay_utility.MchConfig, request *UnionCloseRequest) (err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{combine_out_trade_no}", url.PathEscape(*request.CombineOutTradeNo), -1)
reqBody, err := json.Marshal(request)
if err != nil {
return err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return err
}
return nil
} else {
return wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionCloseRequest struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
SubOrders []UnionCloseSubOrder `json:"sub_orders,omitempty"`
}
func (o *UnionCloseRequest) MarshalJSON() ([]byte, error) {
type Alias UnionCloseRequest
a := &struct {
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
CombineOutTradeNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type UnionCloseSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
}

View File

@@ -0,0 +1,195 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionApiv3H5PrepayRequest{
CombineAppid: wxpay_utility.String("wxd678efh567hg6787"),
CombineOutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
CombineMchid: wxpay_utility.String("1230000109"),
SceneInfo: &UnionH5SceneInfo{
PayerClientIp: wxpay_utility.String("14.23.150.211"),
DeviceId: wxpay_utility.String("013467007045764"),
H5Info: &UnionH5Info{
Type: wxpay_utility.String("iOS"),
AppName: wxpay_utility.String("王者荣耀"),
AppUrl: wxpay_utility.String("https://pay.qq.com"),
BundleId: wxpay_utility.String("com.tencent.wzryiOS"),
PackageName: wxpay_utility.String("com.tencent.tmgp.sgame"),
},
},
SubOrders: []UnionCommonSubOrder{
UnionCommonSubOrder{
Mchid: wxpay_utility.String("1230000109"),
OutTradeNo: wxpay_utility.String("20150806125346"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("深圳分店"),
Description: wxpay_utility.String("腾讯充值中心-QQ会员充值"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
UnionCommonSubOrder{
Mchid: wxpay_utility.String("1230000119"),
OutTradeNo: wxpay_utility.String("20150806125347"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("广州分店"),
Description: wxpay_utility.String("腾讯充值中心-微信充值"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
},
TimeExpire: wxpay_utility.Time(time.Now()),
NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
}
response, err := UnionH5Prepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnionH5Prepay(config *wxpay_utility.MchConfig, request *UnionApiv3H5PrepayRequest) (response *UnionApiv3H5PrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/combine-transactions/h5"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &UnionApiv3H5PrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionApiv3H5PrepayRequest struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
CombineMchid *string `json:"combine_mchid,omitempty"`
SceneInfo *UnionH5SceneInfo `json:"scene_info,omitempty"`
SubOrders []UnionCommonSubOrder `json:"sub_orders,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
}
type UnionApiv3H5PrepayResponse struct {
H5Url *string `json:"h5_url,omitempty"`
}
type UnionH5SceneInfo struct {
PayerClientIp *string `json:"payer_client_ip,omitempty"`
DeviceId *string `json:"device_id,omitempty"`
H5Info *UnionH5Info `json:"h5_info,omitempty"`
}
type UnionCommonSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Amount *UnionAmountInfo `json:"amount,omitempty"`
Attach *string `json:"attach,omitempty"`
Description *string `json:"description,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SettleInfo *UnionSettleInfo `json:"settle_info,omitempty"`
}
type UnionH5Info struct {
Type *string `json:"type,omitempty"`
AppName *string `json:"app_name,omitempty"`
AppUrl *string `json:"app_url,omitempty"`
BundleId *string `json:"bundle_id,omitempty"`
PackageName *string `json:"package_name,omitempty"`
}
type UnionAmountInfo struct {
TotalAmount *int64 `json:"total_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type UnionSettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}

View File

@@ -0,0 +1,191 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionApiv3JsapiPrepayRequest{
CombineAppid: wxpay_utility.String("wxd678efh567hg6787"),
CombineMchid: wxpay_utility.String("1230000109"),
CombineOutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
CombinePayerInfo: &UnionPayerInfo{
Openid: wxpay_utility.String("oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"),
},
SceneInfo: &UnionSceneInfo{
DeviceId: wxpay_utility.String("POS1:1"),
PayerClientIp: wxpay_utility.String("14.17.22.32"),
},
SubOrders: []UnionSubOrder{
UnionSubOrder{
Mchid: wxpay_utility.String("1230000109"),
OutTradeNo: wxpay_utility.String("20150806125346"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("深圳分店"),
Description: wxpay_utility.String("腾讯充值中心-QQ会员充值"),
Detail: wxpay_utility.String("买单费用"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
UnionSubOrder{
Mchid: wxpay_utility.String("1230000119"),
OutTradeNo: wxpay_utility.String("20150806125347"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("广州分店"),
Description: wxpay_utility.String("腾讯充值中心-微信充值"),
Detail: wxpay_utility.String("买单费用"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
},
TimeExpire: wxpay_utility.Time(time.Now()),
NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
}
response, err := UnionJsapiPrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnionJsapiPrepay(config *wxpay_utility.MchConfig, request *UnionApiv3JsapiPrepayRequest) (response *UnionApiv3JsapiPrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/combine-transactions/jsapi"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &UnionApiv3JsapiPrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionApiv3JsapiPrepayRequest struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineMchid *string `json:"combine_mchid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
CombinePayerInfo *UnionPayerInfo `json:"combine_payer_info,omitempty"`
SceneInfo *UnionSceneInfo `json:"scene_info,omitempty"`
SubOrders []UnionSubOrder `json:"sub_orders,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
}
type UnionApiv3JsapiPrepayResponse struct {
PrepayId *string `json:"prepay_id,omitempty"`
}
type UnionPayerInfo struct {
Openid *string `json:"openid,omitempty"`
SubOpenid *string `json:"sub_openid,omitempty"`
}
type UnionSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
PayerClientIp *string `json:"payer_client_ip,omitempty"`
}
type UnionSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Amount *UnionAmountInfo `json:"amount,omitempty"`
Attach *string `json:"attach,omitempty"`
Description *string `json:"description,omitempty"`
Detail *string `json:"detail,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SettleInfo *UnionSettleInfo `json:"settle_info,omitempty"`
}
type UnionAmountInfo struct {
TotalAmount *int64 `json:"total_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type UnionSettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}

View File

@@ -0,0 +1,179 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionApiv3NativePrepayRequest{
CombineAppid: wxpay_utility.String("wxd678efh567hg6787"),
CombineOutTradeNo: wxpay_utility.String("20150806125346"),
CombineMchid: wxpay_utility.String("1900000109"),
SceneInfo: &UnionSceneInfo{
DeviceId: wxpay_utility.String("POS1:1"),
PayerClientIp: wxpay_utility.String("14.17.22.32"),
},
SubOrders: []UnionCommonSubOrder{
UnionCommonSubOrder{
Mchid: wxpay_utility.String("1230000109"),
OutTradeNo: wxpay_utility.String("20150806125346"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("深圳分店"),
Description: wxpay_utility.String("腾讯充值中心-QQ会员充值"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
UnionCommonSubOrder{
Mchid: wxpay_utility.String("1230000119"),
OutTradeNo: wxpay_utility.String("20150806125347"),
Amount: &UnionAmountInfo{
TotalAmount: wxpay_utility.Int64(10),
Currency: wxpay_utility.String("CNY"),
},
Attach: wxpay_utility.String("广州分店"),
Description: wxpay_utility.String("腾讯充值中心-微信充值"),
GoodsTag: wxpay_utility.String("WXG"),
SettleInfo: &UnionSettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
},
},
TimeExpire: wxpay_utility.Time(time.Now()),
NotifyUrl: wxpay_utility.String("https://yourapp.com/notify"),
}
response, err := UnionNativePrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnionNativePrepay(config *wxpay_utility.MchConfig, request *UnionApiv3NativePrepayRequest) (response *UnionApiv3NativePrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/combine-transactions/native"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &UnionApiv3NativePrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionApiv3NativePrepayRequest struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
CombineMchid *string `json:"combine_mchid,omitempty"`
SceneInfo *UnionSceneInfo `json:"scene_info,omitempty"`
SubOrders []UnionCommonSubOrder `json:"sub_orders,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
}
type UnionApiv3NativePrepayResponse struct {
CodeUrl *string `json:"code_url,omitempty"`
}
type UnionSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
PayerClientIp *string `json:"payer_client_ip,omitempty"`
}
type UnionCommonSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Amount *UnionAmountInfo `json:"amount,omitempty"`
Attach *string `json:"attach,omitempty"`
Description *string `json:"description,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SettleInfo *UnionSettleInfo `json:"settle_info,omitempty"`
}
type UnionAmountInfo struct {
TotalAmount *int64 `json:"total_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type UnionSettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}

View File

@@ -0,0 +1,178 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnionQueryByOutTradeNoRequest{
CombineOutTradeNo: wxpay_utility.String("P20150806125346"),
}
response, err := UnionQueryByOutTradeNo(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnionQueryByOutTradeNo(config *wxpay_utility.MchConfig, request *UnionQueryByOutTradeNoRequest) (response *UnionApiv3UnionQueryResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/combine-transactions/out-trade-no/{combine_out_trade_no}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{combine_out_trade_no}", url.PathEscape(*request.CombineOutTradeNo), -1)
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &UnionApiv3UnionQueryResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnionQueryByOutTradeNoRequest struct {
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
}
func (o *UnionQueryByOutTradeNoRequest) MarshalJSON() ([]byte, error) {
type Alias UnionQueryByOutTradeNoRequest
a := &struct {
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
CombineOutTradeNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type UnionApiv3UnionQueryResponse struct {
CombineAppid *string `json:"combine_appid,omitempty"`
CombineMchid *string `json:"combine_mchid,omitempty"`
CombineOutTradeNo *string `json:"combine_out_trade_no,omitempty"`
CombinePayerInfo *UnionCommRespPayerInfo `json:"combine_payer_info,omitempty"`
SceneInfo *UnionCommRespSceneInfo `json:"scene_info,omitempty"`
SubOrders []UnionSubOrder `json:"sub_orders,omitempty"`
}
type UnionCommRespPayerInfo struct {
Openid *string `json:"openid,omitempty"`
}
type UnionCommRespSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
}
type UnionSubOrder struct {
Mchid *string `json:"mchid,omitempty"`
SubMchid *string `json:"sub_mchid,omitempty"`
SubAppid *string `json:"sub_appid,omitempty"`
SubOpenid *string `json:"sub_openid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
TradeType *string `json:"trade_type,omitempty"`
TradeState *string `json:"trade_state,omitempty"`
BankType *string `json:"bank_type,omitempty"`
Attach *string `json:"attach,omitempty"`
SuccessTime *string `json:"success_time,omitempty"`
Amount *UnionCommRespAmountInfo `json:"amount,omitempty"`
PromotionDetail []UnionPromotionDetail `json:"promotion_detail,omitempty"`
}
type UnionCommRespAmountInfo struct {
TotalAmount *int64 `json:"total_amount,omitempty"`
PayerAmount *int64 `json:"payer_amount,omitempty"`
Currency *string `json:"currency,omitempty"`
PayerCurrency *string `json:"payer_currency,omitempty"`
SettlementRate *int64 `json:"settlement_rate,omitempty"`
}
type UnionPromotionDetail struct {
CouponId *string `json:"coupon_id,omitempty"`
Name *string `json:"name,omitempty"`
Scope *string `json:"scope,omitempty"`
Type *string `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
StockId *string `json:"stock_id,omitempty"`
WechatpayContribute *int64 `json:"wechatpay_contribute,omitempty"`
MerchantContribute *int64 `json:"merchant_contribute,omitempty"`
OtherContribute *int64 `json:"other_contribute,omitempty"`
Currency *string `json:"currency,omitempty"`
GoodsDetail []GoodsDetailInPromotion `json:"goods_detail,omitempty"`
}
type GoodsDetailInPromotion struct {
GoodsId *string `json:"goods_id,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
DiscountAmount *int64 `json:"discount_amount,omitempty"`
GoodsRemark *string `json:"goods_remark,omitempty"`
}

View File

@@ -0,0 +1,156 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &AddReceiverRequest{
Appid: wxpay_utility.String("wx8888888888888888"),
Type: RECEIVERTYPE_MERCHANT_ID.Ptr(),
Account: wxpay_utility.String("86693852"),
Name: wxpay_utility.String("hu89ohu89ohu89o"), /*请传入wxpay_utility.EncryptOAEPWithPublicKey 加密结果*/
RelationType: RECEIVERRELATIONTYPE_STORE.Ptr(),
CustomRelation: wxpay_utility.String("代理商"),
}
response, err := AddReceiver(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func AddReceiver(config *wxpay_utility.MchConfig, request *AddReceiverRequest) (response *AddReceiverResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/profitsharing/receivers/add"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &AddReceiverResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type AddReceiverRequest struct {
Appid *string `json:"appid,omitempty"`
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Name *string `json:"name,omitempty"`
RelationType *ReceiverRelationType `json:"relation_type,omitempty"`
CustomRelation *string `json:"custom_relation,omitempty"`
}
type AddReceiverResponse struct {
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Name *string `json:"name,omitempty"`
RelationType *ReceiverRelationType `json:"relation_type,omitempty"`
CustomRelation *string `json:"custom_relation,omitempty"`
}
type ReceiverType string
func (e ReceiverType) Ptr() *ReceiverType {
return &e
}
const (
RECEIVERTYPE_MERCHANT_ID ReceiverType = "MERCHANT_ID"
RECEIVERTYPE_PERSONAL_OPENID ReceiverType = "PERSONAL_OPENID"
)
type ReceiverRelationType string
func (e ReceiverRelationType) Ptr() *ReceiverRelationType {
return &e
}
const (
RECEIVERRELATIONTYPE_STORE ReceiverRelationType = "STORE"
RECEIVERRELATIONTYPE_STAFF ReceiverRelationType = "STAFF"
RECEIVERRELATIONTYPE_STORE_OWNER ReceiverRelationType = "STORE_OWNER"
RECEIVERRELATIONTYPE_PARTNER ReceiverRelationType = "PARTNER"
RECEIVERRELATIONTYPE_HEADQUARTER ReceiverRelationType = "HEADQUARTER"
RECEIVERRELATIONTYPE_BRAND ReceiverRelationType = "BRAND"
RECEIVERRELATIONTYPE_DISTRIBUTOR ReceiverRelationType = "DISTRIBUTOR"
RECEIVERRELATIONTYPE_USER ReceiverRelationType = "USER"
RECEIVERRELATIONTYPE_SUPPLIER ReceiverRelationType = "SUPPLIER"
RECEIVERRELATIONTYPE_CUSTOM ReceiverRelationType = "CUSTOM"
)
删除分账接收方

View File

@@ -0,0 +1,178 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx",
"1DDE55AD98Exxxxxxxxxx",
"/path/to/apiclient_key.pem",
"PUB_KEY_ID_xxxxxxxxxxxxx",
"/path/to/wxp_pub.pem",
)
if err != nil {
fmt.Println(err)
return
}
request := &CreateOrderRequest{
Appid: wxpay_utility.String("wx8888888888888888"),
TransactionId: wxpay_utility.String("4208450740201411110007820472"),
OutOrderNo: wxpay_utility.String("P20150806125346"),
Receivers: []CreateOrderReceiver{CreateOrderReceiver{
Type: wxpay_utility.String("MERCHANT_ID"),
Account: wxpay_utility.String("86693852"),
Name: wxpay_utility.String("hu89ohu89ohu89o"), /*请传入wxpay_utility.EncryptOAEPWithPublicKey 加密结果*/
Amount: wxpay_utility.Int64(888),
Description: wxpay_utility.String("分给商户A"),
}},
UnfreezeUnsplit: wxpay_utility.Bool(true),
}
response, err := CreateOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
return
}
fmt.Printf("请求成功: %+v\n", response)
}
func CreateOrder(config *wxpay_utility.MchConfig, request *CreateOrderRequest) (response *OrdersEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/profitsharing/orders"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
err = wxpay_utility.ValidateResponse(config.WechatPayPublicKeyId(), config.WechatPayPublicKey(), &httpResponse.Header, respBody)
if err != nil {
return nil, err
}
response := &OrdersEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(httpResponse.StatusCode, httpResponse.Header, respBody)
}
}
type CreateOrderRequest struct {
Appid *string `json:"appid,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
Receivers []CreateOrderReceiver `json:"receivers,omitempty"`
UnfreezeUnsplit *bool `json:"unfreeze_unsplit,omitempty"`
}
type OrdersEntity struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OrderId *string `json:"order_id,omitempty"`
State *OrderStatus `json:"state,omitempty"`
Receivers []OrderReceiverDetail `json:"receivers,omitempty"`
}
type CreateOrderReceiver struct {
Type *string `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Name *string `json:"name,omitempty"`
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
}
type OrderStatus string
func (e OrderStatus) Ptr() *OrderStatus { return &e }
const (
ORDERSTATUS_PROCESSING OrderStatus = "PROCESSING"
ORDERSTATUS_FINISHED OrderStatus = "FINISHED"
)
type OrderReceiverDetail struct {
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Result *DetailStatus `json:"result,omitempty"`
FailReason *DetailFailReason `json:"fail_reason,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
DetailId *string `json:"detail_id,omitempty"`
}
type ReceiverType string
func (e ReceiverType) Ptr() *ReceiverType { return &e }
const (
RECEIVERTYPE_MERCHANT_ID ReceiverType = "MERCHANT_ID"
RECEIVERTYPE_PERSONAL_OPENID ReceiverType = "PERSONAL_OPENID"
)
type DetailStatus string
func (e DetailStatus) Ptr() *DetailStatus { return &e }
const (
DETAILSTATUS_PENDING DetailStatus = "PENDING"
DETAILSTATUS_SUCCESS DetailStatus = "SUCCESS"
DETAILSTATUS_CLOSED DetailStatus = "CLOSED"
)
type DetailFailReason string
func (e DetailFailReason) Ptr() *DetailFailReason { return &e }
const (
DETAILFAILREASON_ACCOUNT_ABNORMAL DetailFailReason = "ACCOUNT_ABNORMAL"
DETAILFAILREASON_NO_RELATION DetailFailReason = "NO_RELATION"
DETAILFAILREASON_RECEIVER_HIGH_RISK DetailFailReason = "RECEIVER_HIGH_RISK"
DETAILFAILREASON_RECEIVER_REAL_NAME_NOT_VERIFIED DetailFailReason = "RECEIVER_REAL_NAME_NOT_VERIFIED"
DETAILFAILREASON_NO_AUTH DetailFailReason = "NO_AUTH"
DETAILFAILREASON_RECEIVER_RECEIPT_LIMIT DetailFailReason = "RECEIVER_RECEIPT_LIMIT"
DETAILFAILREASON_PAYER_ACCOUNT_ABNORMAL DetailFailReason = "PAYER_ACCOUNT_ABNORMAL"
DETAILFAILREASON_INVALID_REQUEST DetailFailReason = "INVALID_REQUEST"
)

View File

@@ -0,0 +1,159 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CreateReturnOrderRequest{
OrderId: wxpay_utility.String("3008450740201411110007820472"),
OutOrderNo: wxpay_utility.String("P20150806125346"),
OutReturnNo: wxpay_utility.String("R20190516001"),
ReturnMchid: wxpay_utility.String("86693852"),
Amount: wxpay_utility.Int64(10),
Description: wxpay_utility.String("用户退款"),
}
response, err := CreateReturnOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func CreateReturnOrder(config *wxpay_utility.MchConfig, request *CreateReturnOrderRequest) (response *ReturnOrdersEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/profitsharing/return-orders"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &ReturnOrdersEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CreateReturnOrderRequest struct {
OrderId *string `json:"order_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OutReturnNo *string `json:"out_return_no,omitempty"`
ReturnMchid *string `json:"return_mchid,omitempty"`
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
}
type ReturnOrdersEntity struct {
OrderId *string `json:"order_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OutReturnNo *string `json:"out_return_no,omitempty"`
ReturnId *string `json:"return_id,omitempty"`
ReturnMchid *string `json:"return_mchid,omitempty"`
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
Result *ReturnOrderStatus `json:"result,omitempty"`
FailReason *ReturnOrderFailReason `json:"fail_reason,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
}
type ReturnOrderStatus string
func (e ReturnOrderStatus) Ptr() *ReturnOrderStatus {
return &e
}
const (
RETURNORDERSTATUS_PROCESSING ReturnOrderStatus = "PROCESSING"
RETURNORDERSTATUS_SUCCESS ReturnOrderStatus = "SUCCESS"
RETURNORDERSTATUS_FAILED ReturnOrderStatus = "FAILED"
)
type ReturnOrderFailReason string
func (e ReturnOrderFailReason) Ptr() *ReturnOrderFailReason {
return &e
}
const (
RETURNORDERFAILREASON_ACCOUNT_ABNORMAL ReturnOrderFailReason = "ACCOUNT_ABNORMAL"
RETURNORDERFAILREASON_BALANCE_NOT_ENOUGH ReturnOrderFailReason = "BALANCE_NOT_ENOUGH"
RETURNORDERFAILREASON_TIME_OUT_CLOSED ReturnOrderFailReason = "TIME_OUT_CLOSED"
RETURNORDERFAILREASON_PAYER_ACCOUNT_ABNORMAL ReturnOrderFailReason = "PAYER_ACCOUNT_ABNORMAL"
RETURNORDERFAILREASON_INVALID_REQUEST ReturnOrderFailReason = "INVALID_REQUEST"
)
查询分账回退结果

View File

@@ -0,0 +1,128 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &DeleteReceiverRequest{
Appid: wxpay_utility.String("wx8888888888888888"),
Type: RECEIVERTYPE_MERCHANT_ID.Ptr(),
Account: wxpay_utility.String("1900000109"),
}
response, err := DeleteReceiver(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func DeleteReceiver(config *wxpay_utility.MchConfig, request *DeleteReceiverRequest) (response *DeleteReceiverResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/profitsharing/receivers/delete"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DeleteReceiverResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type DeleteReceiverRequest struct {
Appid *string `json:"appid,omitempty"`
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
}
type DeleteReceiverResponse struct {
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
}
type ReceiverType string
func (e ReceiverType) Ptr() *ReceiverType {
return &e
}
const (
RECEIVERTYPE_MERCHANT_ID ReceiverType = "MERCHANT_ID"
RECEIVERTYPE_PERSONAL_OPENID ReceiverType = "PERSONAL_OPENID"
)
申请分账账单

View File

@@ -0,0 +1,198 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryOrderRequest{
TransactionId: wxpay_utility.String("4208450740201411110007820472"),
OutOrderNo: wxpay_utility.String("P20150806125346"),
}
response, err := QueryOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func QueryOrder(config *wxpay_utility.MchConfig, request *QueryOrderRequest) (response *OrdersEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/profitsharing/orders/{out_order_no}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{out_order_no}", url.PathEscape(*request.OutOrderNo), -1)
query := reqUrl.Query()
if request.TransactionId != nil {
query.Add("transaction_id", *request.TransactionId)
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &OrdersEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryOrderRequest struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
}
func (o *QueryOrderRequest) MarshalJSON() ([]byte, error) {
type Alias QueryOrderRequest
a := &struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
TransactionId: nil,
OutOrderNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type OrdersEntity struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OrderId *string `json:"order_id,omitempty"`
State *OrderStatus `json:"state,omitempty"`
Receivers []OrderReceiverDetail `json:"receivers,omitempty"`
}
type OrderStatus string
func (e OrderStatus) Ptr() *OrderStatus {
return &e
}
const (
ORDERSTATUS_PROCESSING OrderStatus = "PROCESSING"
ORDERSTATUS_FINISHED OrderStatus = "FINISHED"
)
type OrderReceiverDetail struct {
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Result *DetailStatus `json:"result,omitempty"`
FailReason *DetailFailReason `json:"fail_reason,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
DetailId *string `json:"detail_id,omitempty"`
}
type ReceiverType string
func (e ReceiverType) Ptr() *ReceiverType {
return &e
}
const (
RECEIVERTYPE_MERCHANT_ID ReceiverType = "MERCHANT_ID"
RECEIVERTYPE_PERSONAL_OPENID ReceiverType = "PERSONAL_OPENID"
)
type DetailStatus string
func (e DetailStatus) Ptr() *DetailStatus {
return &e
}
const (
DETAILSTATUS_PENDING DetailStatus = "PENDING"
DETAILSTATUS_SUCCESS DetailStatus = "SUCCESS"
DETAILSTATUS_CLOSED DetailStatus = "CLOSED"
)
type DetailFailReason string
func (e DetailFailReason) Ptr() *DetailFailReason {
return &e
}
const (
DETAILFAILREASON_ACCOUNT_ABNORMAL DetailFailReason = "ACCOUNT_ABNORMAL"
DETAILFAILREASON_NO_RELATION DetailFailReason = "NO_RELATION"
DETAILFAILREASON_RECEIVER_HIGH_RISK DetailFailReason = "RECEIVER_HIGH_RISK"
DETAILFAILREASON_RECEIVER_REAL_NAME_NOT_VERIFIED DetailFailReason = "RECEIVER_REAL_NAME_NOT_VERIFIED"
DETAILFAILREASON_NO_AUTH DetailFailReason = "NO_AUTH"
DETAILFAILREASON_RECEIVER_RECEIPT_LIMIT DetailFailReason = "RECEIVER_RECEIPT_LIMIT"
DETAILFAILREASON_PAYER_ACCOUNT_ABNORMAL DetailFailReason = "PAYER_ACCOUNT_ABNORMAL"
DETAILFAILREASON_INVALID_REQUEST DetailFailReason = "INVALID_REQUEST"
)
请求分账回退

View File

@@ -0,0 +1,122 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryOrderAmountRequest{
TransactionId: wxpay_utility.String("4208450740201411110007820472"),
}
response, err := QueryOrderAmount(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func QueryOrderAmount(config *wxpay_utility.MchConfig, request *QueryOrderAmountRequest) (response *QueryOrderAmountResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/profitsharing/transactions/{transaction_id}/amounts"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{transaction_id}", url.PathEscape(*request.TransactionId), -1)
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &QueryOrderAmountResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryOrderAmountRequest struct {
TransactionId *string `json:"transaction_id,omitempty"`
}
func (o *QueryOrderAmountRequest) MarshalJSON() ([]byte, error) {
type Alias QueryOrderAmountRequest
a := &struct {
TransactionId *string `json:"transaction_id,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
TransactionId: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type QueryOrderAmountResponse struct {
TransactionId *string `json:"transaction_id,omitempty"`
UnsplitAmount *int64 `json:"unsplit_amount,omitempty"`
}
添加分账接收方

View File

@@ -0,0 +1,167 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryReturnOrderRequest{
OutReturnNo: wxpay_utility.String("R20190516001"),
OutOrderNo: wxpay_utility.String("P20190806125346"),
}
response, err := QueryReturnOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func QueryReturnOrder(config *wxpay_utility.MchConfig, request *QueryReturnOrderRequest) (response *ReturnOrdersEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/profitsharing/return-orders/{out_return_no}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{out_return_no}", url.PathEscape(*request.OutReturnNo), -1)
query := reqUrl.Query()
if request.OutOrderNo != nil {
query.Add("out_order_no", *request.OutOrderNo)
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &ReturnOrdersEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryReturnOrderRequest struct {
OutReturnNo *string `json:"out_return_no,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
}
func (o *QueryReturnOrderRequest) MarshalJSON() ([]byte, error) {
type Alias QueryReturnOrderRequest
a := &struct {
OutReturnNo *string `json:"out_return_no,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
OutReturnNo: nil,
OutOrderNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type ReturnOrdersEntity struct {
OrderId *string `json:"order_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OutReturnNo *string `json:"out_return_no,omitempty"`
ReturnId *string `json:"return_id,omitempty"`
ReturnMchid *string `json:"return_mchid,omitempty"`
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
Result *ReturnOrderStatus `json:"result,omitempty"`
FailReason *ReturnOrderFailReason `json:"fail_reason,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
}
type ReturnOrderStatus string
func (e ReturnOrderStatus) Ptr() *ReturnOrderStatus {
return &e
}
const (
RETURNORDERSTATUS_PROCESSING ReturnOrderStatus = "PROCESSING"
RETURNORDERSTATUS_SUCCESS ReturnOrderStatus = "SUCCESS"
RETURNORDERSTATUS_FAILED ReturnOrderStatus = "FAILED"
)
type ReturnOrderFailReason string
func (e ReturnOrderFailReason) Ptr() *ReturnOrderFailReason {
return &e
}
const (
RETURNORDERFAILREASON_ACCOUNT_ABNORMAL ReturnOrderFailReason = "ACCOUNT_ABNORMAL"
RETURNORDERFAILREASON_BALANCE_NOT_ENOUGH ReturnOrderFailReason = "BALANCE_NOT_ENOUGH"
RETURNORDERFAILREASON_TIME_OUT_CLOSED ReturnOrderFailReason = "TIME_OUT_CLOSED"
RETURNORDERFAILREASON_PAYER_ACCOUNT_ABNORMAL ReturnOrderFailReason = "PAYER_ACCOUNT_ABNORMAL"
RETURNORDERFAILREASON_INVALID_REQUEST ReturnOrderFailReason = "INVALID_REQUEST"
)
解冻剩余资金

View File

@@ -0,0 +1,151 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &SplitBillRequest{
BillDate: wxpay_utility.String("2019-06-11"),
TarType: SPLITBILLTARTYPE_GZIP.Ptr(),
}
response, err := SplitBill(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func SplitBill(config *wxpay_utility.MchConfig, request *SplitBillRequest) (response *SplitBillResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/profitsharing/bills"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
query := reqUrl.Query()
if request.BillDate != nil {
query.Add("bill_date", *request.BillDate)
}
if request.TarType != nil {
query.Add("tar_type", fmt.Sprintf("%v", *request.TarType))
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &SplitBillResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type SplitBillRequest struct {
BillDate *string `json:"bill_date,omitempty"`
TarType *SplitBillTarType `json:"tar_type,omitempty"`
}
func (o *SplitBillRequest) MarshalJSON() ([]byte, error) {
type Alias SplitBillRequest
a := &struct {
BillDate *string `json:"bill_date,omitempty"`
TarType *SplitBillTarType `json:"tar_type,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
BillDate: nil,
TarType: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type SplitBillResponse struct {
HashType *SplitBillHashType `json:"hash_type,omitempty"`
HashValue *string `json:"hash_value,omitempty"`
DownloadUrl *string `json:"download_url,omitempty"`
}
type SplitBillTarType string
func (e SplitBillTarType) Ptr() *SplitBillTarType {
return &e
}
const (
SPLITBILLTARTYPE_GZIP SplitBillTarType = "GZIP"
)
type SplitBillHashType string
func (e SplitBillHashType) Ptr() *SplitBillHashType {
return &e
}
const (
SPLITBILLHASHTYPE_SHA1 SplitBillHashType = "SHA1"
)

View File

@@ -0,0 +1,184 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &UnfreezeOrderRequest{
TransactionId: wxpay_utility.String("4208450740201411110007820472"),
OutOrderNo: wxpay_utility.String("P20150806125346"),
Description: wxpay_utility.String("解冻全部剩余资金"),
}
response, err := UnfreezeOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func UnfreezeOrder(config *wxpay_utility.MchConfig, request *UnfreezeOrderRequest) (response *OrdersEntity, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/profitsharing/orders/unfreeze"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &OrdersEntity{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type UnfreezeOrderRequest struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
Description *string `json:"description,omitempty"`
}
type OrdersEntity struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutOrderNo *string `json:"out_order_no,omitempty"`
OrderId *string `json:"order_id,omitempty"`
State *OrderStatus `json:"state,omitempty"`
Receivers []OrderReceiverDetail `json:"receivers,omitempty"`
}
type OrderStatus string
func (e OrderStatus) Ptr() *OrderStatus {
return &e
}
const (
ORDERSTATUS_PROCESSING OrderStatus = "PROCESSING"
ORDERSTATUS_FINISHED OrderStatus = "FINISHED"
)
type OrderReceiverDetail struct {
Amount *int64 `json:"amount,omitempty"`
Description *string `json:"description,omitempty"`
Type *ReceiverType `json:"type,omitempty"`
Account *string `json:"account,omitempty"`
Result *DetailStatus `json:"result,omitempty"`
FailReason *DetailFailReason `json:"fail_reason,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
FinishTime *time.Time `json:"finish_time,omitempty"`
DetailId *string `json:"detail_id,omitempty"`
}
type ReceiverType string
func (e ReceiverType) Ptr() *ReceiverType {
return &e
}
const (
RECEIVERTYPE_MERCHANT_ID ReceiverType = "MERCHANT_ID"
RECEIVERTYPE_PERSONAL_OPENID ReceiverType = "PERSONAL_OPENID"
)
type DetailStatus string
func (e DetailStatus) Ptr() *DetailStatus {
return &e
}
const (
DETAILSTATUS_PENDING DetailStatus = "PENDING"
DETAILSTATUS_SUCCESS DetailStatus = "SUCCESS"
DETAILSTATUS_CLOSED DetailStatus = "CLOSED"
)
type DetailFailReason string
func (e DetailFailReason) Ptr() *DetailFailReason {
return &e
}
const (
DETAILFAILREASON_ACCOUNT_ABNORMAL DetailFailReason = "ACCOUNT_ABNORMAL"
DETAILFAILREASON_NO_RELATION DetailFailReason = "NO_RELATION"
DETAILFAILREASON_RECEIVER_HIGH_RISK DetailFailReason = "RECEIVER_HIGH_RISK"
DETAILFAILREASON_RECEIVER_REAL_NAME_NOT_VERIFIED DetailFailReason = "RECEIVER_REAL_NAME_NOT_VERIFIED"
DETAILFAILREASON_NO_AUTH DetailFailReason = "NO_AUTH"
DETAILFAILREASON_RECEIVER_RECEIPT_LIMIT DetailFailReason = "RECEIVER_RECEIPT_LIMIT"
DETAILFAILREASON_PAYER_ACCOUNT_ABNORMAL DetailFailReason = "PAYER_ACCOUNT_ABNORMAL"
DETAILFAILREASON_INVALID_REQUEST DetailFailReason = "INVALID_REQUEST"
)
查询剩余待分金额

View File

@@ -0,0 +1,196 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CommonPrepayRequest{
Appid: wxpay_utility.String("wxd678efh567hg6787"),
Mchid: wxpay_utility.String("1230000109"),
Description: wxpay_utility.String("Image形象店-深圳腾大-QQ公仔"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
TimeExpire: wxpay_utility.Time(time.Now()),
Attach: wxpay_utility.String("自定义数据说明"),
NotifyUrl: wxpay_utility.String(" https://www.weixin.qq.com/wxpay/pay.php"),
GoodsTag: wxpay_utility.String("WXG"),
SupportFapiao: wxpay_utility.Bool(false),
Amount: &CommonAmountInfo{
Total: wxpay_utility.Int64(100),
Currency: wxpay_utility.String("CNY"),
},
Detail: &CouponInfo{
CostPrice: wxpay_utility.Int64(608800),
InvoiceId: wxpay_utility.String("微信123"),
GoodsDetail: []GoodsDetail{GoodsDetail{
MerchantGoodsId: wxpay_utility.String("1246464644"),
WechatpayGoodsId: wxpay_utility.String("1001"),
GoodsName: wxpay_utility.String("iPhoneX 256G"),
Quantity: wxpay_utility.Int64(1),
UnitPrice: wxpay_utility.Int64(528800),
}},
},
SceneInfo: &CommonSceneInfo{
PayerClientIp: wxpay_utility.String("14.23.150.211"),
DeviceId: wxpay_utility.String("013467007045764"),
StoreInfo: &StoreInfo{
Id: wxpay_utility.String("0001"),
Name: wxpay_utility.String("腾讯大厦分店"),
AreaCode: wxpay_utility.String("440305"),
Address: wxpay_utility.String("广东省深圳市南山区科技中一道10000号"),
},
},
SettleInfo: &SettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
}
response, err := AppPrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// AppPrepay App下单
func AppPrepay(config *wxpay_utility.MchConfig, request *CommonPrepayRequest) (response *DirectApiv3AppPrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/pay/transactions/app"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3AppPrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CommonPrepayRequest struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
Description *string `json:"description,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
Attach *string `json:"attach,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SupportFapiao *bool `json:"support_fapiao,omitempty"`
Amount *CommonAmountInfo `json:"amount,omitempty"`
Detail *CouponInfo `json:"detail,omitempty"`
SceneInfo *CommonSceneInfo `json:"scene_info,omitempty"`
SettleInfo *SettleInfo `json:"settle_info,omitempty"`
}
type DirectApiv3AppPrepayResponse struct {
PrepayId *string `json:"prepay_id,omitempty"`
}
type CommonAmountInfo struct {
Total *int64 `json:"total,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type CouponInfo struct {
CostPrice *int64 `json:"cost_price,omitempty"`
InvoiceId *string `json:"invoice_id,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type CommonSceneInfo struct {
PayerClientIp *string `json:"payer_client_ip,omitempty"`
DeviceId *string `json:"device_id,omitempty"`
StoreInfo *StoreInfo `json:"store_info,omitempty"`
}
type SettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
}
type StoreInfo struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
AreaCode *string `json:"area_code,omitempty"`
Address *string `json:"address,omitempty"`
}

View File

@@ -0,0 +1,212 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &DirectApiv3H5PrepayRequest{
Appid: wxpay_utility.String("wxd678efh567hg6787"),
Mchid: wxpay_utility.String("1230000109"),
Description: wxpay_utility.String("Image形象店-深圳腾大-QQ公仔"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
TimeExpire: wxpay_utility.Time(time.Now()),
Attach: wxpay_utility.String("自定义数据说明"),
NotifyUrl: wxpay_utility.String(" https://www.weixin.qq.com/wxpay/pay.php"),
GoodsTag: wxpay_utility.String("WXG"),
SupportFapiao: wxpay_utility.Bool(false),
Amount: &CommonAmountInfo{
Total: wxpay_utility.Int64(100),
Currency: wxpay_utility.String("CNY"),
},
Detail: &CouponInfo{
CostPrice: wxpay_utility.Int64(608800),
InvoiceId: wxpay_utility.String("微信123"),
GoodsDetail: []GoodsDetail{GoodsDetail{
MerchantGoodsId: wxpay_utility.String("1246464644"),
WechatpayGoodsId: wxpay_utility.String("1001"),
GoodsName: wxpay_utility.String("iPhoneX 256G"),
Quantity: wxpay_utility.Int64(1),
UnitPrice: wxpay_utility.Int64(528800),
}},
},
SceneInfo: &H5ReqSceneInfo{
PayerClientIp: wxpay_utility.String("14.23.150.211"),
DeviceId: wxpay_utility.String("013467007045764"),
StoreInfo: &StoreInfo{
Id: wxpay_utility.String("0001"),
Name: wxpay_utility.String("腾讯大厦分店"),
AreaCode: wxpay_utility.String("440305"),
Address: wxpay_utility.String("广东省深圳市南山区科技中一道10000号"),
},
H5Info: &H5Info{
Type: wxpay_utility.String("iOS"),
AppName: wxpay_utility.String("王者荣耀"),
AppUrl: wxpay_utility.String("https://pay.qq.com"),
BundleId: wxpay_utility.String("com.tencent.wzryiOS"),
PackageName: wxpay_utility.String("com.tencent.tmgp.sgame"),
},
},
SettleInfo: &SettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
}
response, err := H5Prepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// H5Prepay H5下单
func H5Prepay(config *wxpay_utility.MchConfig, request *DirectApiv3H5PrepayRequest) (response *DirectApiv3H5PrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/pay/transactions/h5"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3H5PrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type DirectApiv3H5PrepayRequest struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
Description *string `json:"description,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
Attach *string `json:"attach,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SupportFapiao *bool `json:"support_fapiao,omitempty"`
Amount *CommonAmountInfo `json:"amount,omitempty"`
Detail *CouponInfo `json:"detail,omitempty"`
SceneInfo *H5ReqSceneInfo `json:"scene_info,omitempty"`
SettleInfo *SettleInfo `json:"settle_info,omitempty"`
}
type DirectApiv3H5PrepayResponse struct {
H5Url *string `json:"h5_url,omitempty"`
}
type CommonAmountInfo struct {
Total *int64 `json:"total,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type CouponInfo struct {
CostPrice *int64 `json:"cost_price,omitempty"`
InvoiceId *string `json:"invoice_id,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type H5ReqSceneInfo struct {
PayerClientIp *string `json:"payer_client_ip,omitempty"`
DeviceId *string `json:"device_id,omitempty"`
StoreInfo *StoreInfo `json:"store_info,omitempty"`
H5Info *H5Info `json:"h5_info,omitempty"`
}
type SettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
}
type StoreInfo struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
AreaCode *string `json:"area_code,omitempty"`
Address *string `json:"address,omitempty"`
}
type H5Info struct {
Type *string `json:"type,omitempty"`
AppName *string `json:"app_name,omitempty"`
AppUrl *string `json:"app_url,omitempty"`
BundleId *string `json:"bundle_id,omitempty"`
PackageName *string `json:"package_name,omitempty"`
}

View File

@@ -0,0 +1,196 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CommonPrepayRequest{
Appid: wxpay_utility.String("wxd678efh567hg6787"),
Mchid: wxpay_utility.String("1230000109"),
Description: wxpay_utility.String("Image形象店-深圳腾大-QQ公仔"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
TimeExpire: wxpay_utility.Time(time.Now()),
Attach: wxpay_utility.String("自定义数据说明"),
NotifyUrl: wxpay_utility.String(" https://www.weixin.qq.com/wxpay/pay.php"),
GoodsTag: wxpay_utility.String("WXG"),
SupportFapiao: wxpay_utility.Bool(false),
Amount: &CommonAmountInfo{
Total: wxpay_utility.Int64(100),
Currency: wxpay_utility.String("CNY"),
},
Detail: &CouponInfo{
CostPrice: wxpay_utility.Int64(608800),
InvoiceId: wxpay_utility.String("微信123"),
GoodsDetail: []GoodsDetail{GoodsDetail{
MerchantGoodsId: wxpay_utility.String("1246464644"),
WechatpayGoodsId: wxpay_utility.String("1001"),
GoodsName: wxpay_utility.String("iPhoneX 256G"),
Quantity: wxpay_utility.Int64(1),
UnitPrice: wxpay_utility.Int64(528800),
}},
},
SceneInfo: &CommonSceneInfo{
PayerClientIp: wxpay_utility.String("14.23.150.211"),
DeviceId: wxpay_utility.String("013467007045764"),
StoreInfo: &StoreInfo{
Id: wxpay_utility.String("0001"),
Name: wxpay_utility.String("腾讯大厦分店"),
AreaCode: wxpay_utility.String("440305"),
Address: wxpay_utility.String("广东省深圳市南山区科技中一道10000号"),
},
},
SettleInfo: &SettleInfo{
ProfitSharing: wxpay_utility.Bool(false),
},
}
response, err := NativePrepay(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// NativePrepay Native下单
func NativePrepay(config *wxpay_utility.MchConfig, request *CommonPrepayRequest) (response *DirectApiv3DirectNativePrepayResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/pay/transactions/native"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3DirectNativePrepayResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CommonPrepayRequest struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
Description *string `json:"description,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TimeExpire *time.Time `json:"time_expire,omitempty"`
Attach *string `json:"attach,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
GoodsTag *string `json:"goods_tag,omitempty"`
SupportFapiao *bool `json:"support_fapiao,omitempty"`
Amount *CommonAmountInfo `json:"amount,omitempty"`
Detail *CouponInfo `json:"detail,omitempty"`
SceneInfo *CommonSceneInfo `json:"scene_info,omitempty"`
SettleInfo *SettleInfo `json:"settle_info,omitempty"`
}
type DirectApiv3DirectNativePrepayResponse struct {
CodeUrl *string `json:"code_url,omitempty"`
}
type CommonAmountInfo struct {
Total *int64 `json:"total,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type CouponInfo struct {
CostPrice *int64 `json:"cost_price,omitempty"`
InvoiceId *string `json:"invoice_id,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type CommonSceneInfo struct {
PayerClientIp *string `json:"payer_client_ip,omitempty"`
DeviceId *string `json:"device_id,omitempty"`
StoreInfo *StoreInfo `json:"store_info,omitempty"`
}
type SettleInfo struct {
ProfitSharing *bool `json:"profit_sharing,omitempty"`
}
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
}
type StoreInfo struct {
Id *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
AreaCode *string `json:"area_code,omitempty"`
Address *string `json:"address,omitempty"`
}

View File

@@ -0,0 +1,178 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryByOutTradeNoRequest{
Mchid: wxpay_utility.String("1230000109"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
}
response, err := QueryByOutTradeNo(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func QueryByOutTradeNo(config *wxpay_utility.MchConfig, request *QueryByOutTradeNoRequest) (response *DirectApiv3QueryResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/pay/transactions/out-trade-no/{out_trade_no}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{out_trade_no}", url.PathEscape(*request.OutTradeNo), -1)
query := reqUrl.Query()
if request.Mchid != nil {
query.Add("mchid", *request.Mchid)
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3QueryResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryByOutTradeNoRequest struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
}
func (o *QueryByOutTradeNoRequest) MarshalJSON() ([]byte, error) {
type Alias QueryByOutTradeNoRequest
a := &struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
Mchid: nil,
OutTradeNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type DirectApiv3QueryResponse struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
TradeType *string `json:"trade_type,omitempty"`
TradeState *string `json:"trade_state,omitempty"`
TradeStateDesc *string `json:"trade_state_desc,omitempty"`
BankType *string `json:"bank_type,omitempty"`
Attach *string `json:"attach,omitempty"`
SuccessTime *string `json:"success_time,omitempty"`
Payer *CommRespPayerInfo `json:"payer,omitempty"`
Amount *CommRespAmountInfo `json:"amount,omitempty"`
SceneInfo *CommRespSceneInfo `json:"scene_info,omitempty"`
PromotionDetail []PromotionDetail `json:"promotion_detail,omitempty"`
}
type CommRespPayerInfo struct {
Openid *string `json:"openid,omitempty"`
}
type CommRespAmountInfo struct {
Total *int64 `json:"total,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
Currency *string `json:"currency,omitempty"`
PayerCurrency *string `json:"payer_currency,omitempty"`
}
type CommRespSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
}
type PromotionDetail struct {
CouponId *string `json:"coupon_id,omitempty"`
Name *string `json:"name,omitempty"`
Scope *string `json:"scope,omitempty"`
Type *string `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
StockId *string `json:"stock_id,omitempty"`
WechatpayContribute *int64 `json:"wechatpay_contribute,omitempty"`
MerchantContribute *int64 `json:"merchant_contribute,omitempty"`
OtherContribute *int64 `json:"other_contribute,omitempty"`
Currency *string `json:"currency,omitempty"`
GoodsDetail []GoodsDetailInPromotion `json:"goods_detail,omitempty"`
}
type GoodsDetailInPromotion struct {
GoodsId *string `json:"goods_id,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
DiscountAmount *int64 `json:"discount_amount,omitempty"`
GoodsRemark *string `json:"goods_remark,omitempty"`
}

View File

@@ -0,0 +1,178 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryByWxTradeNoRequest{
Mchid: wxpay_utility.String("1230000109"),
TransactionId: wxpay_utility.String("1217752501201407033233368018"),
}
response, err := QueryByWxTradeNo(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
func QueryByWxTradeNo(config *wxpay_utility.MchConfig, request *QueryByWxTradeNoRequest) (response *DirectApiv3QueryResponse, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/pay/transactions/id/{transaction_id}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{transaction_id}", url.PathEscape(*request.TransactionId), -1)
query := reqUrl.Query()
if request.Mchid != nil {
query.Add("mchid", *request.Mchid)
}
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &DirectApiv3QueryResponse{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryByWxTradeNoRequest struct {
Mchid *string `json:"mchid,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
}
func (o *QueryByWxTradeNoRequest) MarshalJSON() ([]byte, error) {
type Alias QueryByWxTradeNoRequest
a := &struct {
Mchid *string `json:"mchid,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
Mchid: nil,
TransactionId: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type DirectApiv3QueryResponse struct {
Appid *string `json:"appid,omitempty"`
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
TradeType *string `json:"trade_type,omitempty"`
TradeState *string `json:"trade_state,omitempty"`
TradeStateDesc *string `json:"trade_state_desc,omitempty"`
BankType *string `json:"bank_type,omitempty"`
Attach *string `json:"attach,omitempty"`
SuccessTime *string `json:"success_time,omitempty"`
Payer *CommRespPayerInfo `json:"payer,omitempty"`
Amount *CommRespAmountInfo `json:"amount,omitempty"`
SceneInfo *CommRespSceneInfo `json:"scene_info,omitempty"`
PromotionDetail []PromotionDetail `json:"promotion_detail,omitempty"`
}
type CommRespPayerInfo struct {
Openid *string `json:"openid,omitempty"`
}
type CommRespAmountInfo struct {
Total *int64 `json:"total,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
Currency *string `json:"currency,omitempty"`
PayerCurrency *string `json:"payer_currency,omitempty"`
}
type CommRespSceneInfo struct {
DeviceId *string `json:"device_id,omitempty"`
}
type PromotionDetail struct {
CouponId *string `json:"coupon_id,omitempty"`
Name *string `json:"name,omitempty"`
Scope *string `json:"scope,omitempty"`
Type *string `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
StockId *string `json:"stock_id,omitempty"`
WechatpayContribute *int64 `json:"wechatpay_contribute,omitempty"`
MerchantContribute *int64 `json:"merchant_contribute,omitempty"`
OtherContribute *int64 `json:"other_contribute,omitempty"`
Currency *string `json:"currency,omitempty"`
GoodsDetail []GoodsDetailInPromotion `json:"goods_detail,omitempty"`
}
type GoodsDetailInPromotion struct {
GoodsId *string `json:"goods_id,omitempty"`
Quantity *int64 `json:"quantity,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
DiscountAmount *int64 `json:"discount_amount,omitempty"`
GoodsRemark *string `json:"goods_remark,omitempty"`
}

View File

@@ -0,0 +1,129 @@
// 关闭订单
//
// 未支付状态的订单,可在无需支付时调用此接口关闭订单。常见关单情况包括:
// 1. 用户在商户系统提交取消订单请求,商户需执行关单操作。
// 2. 订单超时未支付超出商户系统设定的可支付时间或下单时的time_expire支付截止时间商户需进行关单处理。
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CloseOrderRequest{
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
Mchid: wxpay_utility.String("1230000109"),
}
err = CloseOrder(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Println("请求成功")
}
// CloseOrder 关闭订单
//
// 未支付状态的订单,可在无需支付时调用此接口关闭订单。常见关单情况包括:
// 1. 用户在商户系统提交取消订单请求,商户需执行关单操作。
// 2. 订单超时未支付超出商户系统设定的可支付时间或下单时的time_expire支付截止时间商户需进行关单处理。
func CloseOrder(config *wxpay_utility.MchConfig, request *CloseOrderRequest) (err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/pay/transactions/out-trade-no/{out_trade_no}/close"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{out_trade_no}", url.PathEscape(*request.OutTradeNo), -1)
reqBody, err := json.Marshal(request)
if err != nil {
return err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return err
}
return nil
} else {
return wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CloseOrderRequest struct {
Mchid *string `json:"mchid,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
}
func (o *CloseOrderRequest) MarshalJSON() ([]byte, error) {
type Alias CloseOrderRequest
a := &struct {
OutTradeNo *string `json:"out_trade_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
OutTradeNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}

View File

@@ -0,0 +1,269 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CreateAbnormalRefundRequest{
RefundId: wxpay_utility.String("50000000382019052709732678859"),
OutRefundNo: wxpay_utility.String("1217752501201407033233368018"),
Type: ABNORMALRECEIVETYPE_MERCHANT_BANK_CARD.Ptr(),
BankType: wxpay_utility.String("ICBC_DEBIT"),
BankAccount: wxpay_utility.String("d+xT+MQCvrLHUVDWv/8MR/dB7TkXLVfSrUxMPZy6jWWYzpRrEEaYQE8ZRGYoeorwC+w=="), /*请传入wxpay_utility.EncryptOAEPWithPublicKey 加密结果*/
RealName: wxpay_utility.String("UPgQcZSdq3zOayJwZ5XLrHY2dZU1W2Cd"), /*请传入wxpay_utility.EncryptOAEPWithPublicKey 加密结果*/
}
response, err := CreateAbnormalRefund(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// CreateAbnormalRefund 发起异常退款
func CreateAbnormalRefund(config *wxpay_utility.MchConfig, request *CreateAbnormalRefundRequest) (response *Refund, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{refund_id}", url.PathEscape(*request.RefundId), -1)
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &Refund{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CreateAbnormalRefundRequest struct {
RefundId *string `json:"refund_id,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
Type *AbnormalReceiveType `json:"type,omitempty"`
BankType *string `json:"bank_type,omitempty"`
BankAccount *string `json:"bank_account,omitempty"`
RealName *string `json:"real_name,omitempty"`
}
func (o *CreateAbnormalRefundRequest) MarshalJSON() ([]byte, error) {
type Alias CreateAbnormalRefundRequest
a := &struct {
RefundId *string `json:"refund_id,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
RefundId: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type Refund struct {
RefundId *string `json:"refund_id,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Channel *Channel `json:"channel,omitempty"`
UserReceivedAccount *string `json:"user_received_account,omitempty"`
SuccessTime *time.Time `json:"success_time,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
Status *Status `json:"status,omitempty"`
FundsAccount *FundsAccount `json:"funds_account,omitempty"`
Amount *Amount `json:"amount,omitempty"`
PromotionDetail []Promotion `json:"promotion_detail,omitempty"`
}
type AbnormalReceiveType string
func (e AbnormalReceiveType) Ptr() *AbnormalReceiveType {
return &e
}
const (
ABNORMALRECEIVETYPE_USER_BANK_CARD AbnormalReceiveType = "USER_BANK_CARD"
ABNORMALRECEIVETYPE_MERCHANT_BANK_CARD AbnormalReceiveType = "MERCHANT_BANK_CARD"
)
type Channel string
func (e Channel) Ptr() *Channel {
return &e
}
const (
CHANNEL_ORIGINAL Channel = "ORIGINAL"
CHANNEL_BALANCE Channel = "BALANCE"
CHANNEL_OTHER_BALANCE Channel = "OTHER_BALANCE"
CHANNEL_OTHER_BANKCARD Channel = "OTHER_BANKCARD"
)
type Status string
func (e Status) Ptr() *Status {
return &e
}
const (
STATUS_SUCCESS Status = "SUCCESS"
STATUS_CLOSED Status = "CLOSED"
STATUS_PROCESSING Status = "PROCESSING"
STATUS_ABNORMAL Status = "ABNORMAL"
)
type FundsAccount string
func (e FundsAccount) Ptr() *FundsAccount {
return &e
}
const (
FUNDSACCOUNT_UNSETTLED FundsAccount = "UNSETTLED"
FUNDSACCOUNT_AVAILABLE FundsAccount = "AVAILABLE"
FUNDSACCOUNT_UNAVAILABLE FundsAccount = "UNAVAILABLE"
FUNDSACCOUNT_OPERATION FundsAccount = "OPERATION"
FUNDSACCOUNT_BASIC FundsAccount = "BASIC"
FUNDSACCOUNT_ECNY_BASIC FundsAccount = "ECNY_BASIC"
)
type Amount struct {
Total *int64 `json:"total,omitempty"`
Refund *int64 `json:"refund,omitempty"`
From []FundsFromItem `json:"from,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
PayerRefund *int64 `json:"payer_refund,omitempty"`
SettlementRefund *int64 `json:"settlement_refund,omitempty"`
SettlementTotal *int64 `json:"settlement_total,omitempty"`
DiscountRefund *int64 `json:"discount_refund,omitempty"`
Currency *string `json:"currency,omitempty"`
RefundFee *int64 `json:"refund_fee,omitempty"`
}
type Promotion struct {
PromotionId *string `json:"promotion_id,omitempty"`
Scope *PromotionScope `json:"scope,omitempty"`
Type *PromotionType `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type FundsFromItem struct {
Account *Account `json:"account,omitempty"`
Amount *int64 `json:"amount,omitempty"`
}
type PromotionScope string
func (e PromotionScope) Ptr() *PromotionScope {
return &e
}
const (
PROMOTIONSCOPE_GLOBAL PromotionScope = "GLOBAL"
PROMOTIONSCOPE_SINGLE PromotionScope = "SINGLE"
)
type PromotionType string
func (e PromotionType) Ptr() *PromotionType {
return &e
}
const (
PROMOTIONTYPE_CASH PromotionType = "CASH"
PROMOTIONTYPE_NOCASH PromotionType = "NOCASH"
)
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
RefundQuantity *int64 `json:"refund_quantity,omitempty"`
}
type Account string
func (e Account) Ptr() *Account {
return &e
}
const (
ACCOUNT_AVAILABLE Account = "AVAILABLE"
ACCOUNT_UNAVAILABLE Account = "UNAVAILABLE"
)

View File

@@ -0,0 +1,300 @@
package main
import (
"bytes"
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
// 退款申请
//
// 支付成功后1年内可通过此接口将款项全部或部分原路退还给用户也可在商户平台手动操作
//
// 关键注意:
// 1. 一笔订单最多50次部分退款重试必须用原 out_refund_no否则会重复退款。
// 2. 接口返回成功仅表示受理成功,实际结果以退款回调通知或查询退款接口为准。
// 3. 原路退还银行卡1-3个工作日到账零钱即时到账。
// 4. 有代金券的订单部分退款时,退给用户 = 退款金额 × (实付 ÷ 总额),四舍五入。
// 5. 有分账的订单,需确保可用余额充足;部分分账未解冻时需先调"完结分账"。
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &CreateRequest{
TransactionId: wxpay_utility.String("1217752501201407033233368018"),
OutTradeNo: wxpay_utility.String("1217752501201407033233368018"),
OutRefundNo: wxpay_utility.String("1217752501201407033233368018"),
Reason: wxpay_utility.String("商品已售完"),
NotifyUrl: wxpay_utility.String("https://weixin.qq.com"),
FundsAccount: REQFUNDSACCOUNT_AVAILABLE.Ptr(),
Amount: &AmountReq{
Refund: wxpay_utility.Int64(888),
From: []FundsFromItem{FundsFromItem{
Account: ACCOUNT_AVAILABLE.Ptr(),
Amount: wxpay_utility.Int64(444),
}},
Total: wxpay_utility.Int64(888),
Currency: wxpay_utility.String("CNY"),
},
GoodsDetail: []GoodsDetail{GoodsDetail{
MerchantGoodsId: wxpay_utility.String("1217752501201407033233368018"),
WechatpayGoodsId: wxpay_utility.String("1001"),
GoodsName: wxpay_utility.String("iPhone6s 16G"),
UnitPrice: wxpay_utility.Int64(528800),
RefundAmount: wxpay_utility.Int64(528800),
RefundQuantity: wxpay_utility.Int64(1),
}},
}
response, err := CreateRefund(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// CreateRefund 退款申请
//
// 在交易完成后的一年内(以支付成功时间为起点+365天计算若因用户或商户方面导致需进行订单退款
// 商户可通过此接口将支付金额的全部或部分原路退还至用户。
//
// 注意:
// 1. 一笔订单最多支持50次部分退款若需多次部分退款请更换商户退款单号并间隔1分钟后再次调用
// 2. 在申请退款失败后进行重试时,请务必使用原商户退款单号,以避免因重复退款而导致的资金损失。
// 3. 同一商户号下此接口调用成功的频率限制为150QPS而调用失败报错时的频率限制为6QPS。
// 4. 申请退款接口返回成功仅表示退款单已受理成功,具体的退款结果需依据退款结果通知及查询退款的返回信息为准。
// 5. 若一个月前的订单申请退款时返回报错"频率限制1个月之前的订单请降低申请频率再重试",请调整退款时间,再使用原参数进行重试。
func CreateRefund(config *wxpay_utility.MchConfig, request *CreateRequest) (response *Refund, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "POST"
path = "/v3/refund/domestic/refunds"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqBody, err := json.Marshal(request)
if err != nil {
return nil, err
}
httpRequest, err := http.NewRequest(method, reqUrl.String(), bytes.NewReader(reqBody))
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
httpRequest.Header.Set("Content-Type", "application/json")
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), reqBody)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &Refund{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type CreateRequest struct {
TransactionId *string `json:"transaction_id,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
Reason *string `json:"reason,omitempty"`
NotifyUrl *string `json:"notify_url,omitempty"`
FundsAccount *ReqFundsAccount `json:"funds_account,omitempty"`
Amount *AmountReq `json:"amount,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type Refund struct {
RefundId *string `json:"refund_id,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Channel *Channel `json:"channel,omitempty"`
UserReceivedAccount *string `json:"user_received_account,omitempty"`
SuccessTime *time.Time `json:"success_time,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
Status *Status `json:"status,omitempty"`
FundsAccount *FundsAccount `json:"funds_account,omitempty"`
Amount *Amount `json:"amount,omitempty"`
PromotionDetail []Promotion `json:"promotion_detail,omitempty"`
}
type ReqFundsAccount string
func (e ReqFundsAccount) Ptr() *ReqFundsAccount {
return &e
}
const (
REQFUNDSACCOUNT_AVAILABLE ReqFundsAccount = "AVAILABLE"
REQFUNDSACCOUNT_UNSETTLED ReqFundsAccount = "UNSETTLED"
)
type AmountReq struct {
Refund *int64 `json:"refund,omitempty"`
From []FundsFromItem `json:"from,omitempty"`
Total *int64 `json:"total,omitempty"`
Currency *string `json:"currency,omitempty"`
}
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
RefundQuantity *int64 `json:"refund_quantity,omitempty"`
}
type Channel string
func (e Channel) Ptr() *Channel {
return &e
}
const (
CHANNEL_ORIGINAL Channel = "ORIGINAL"
CHANNEL_BALANCE Channel = "BALANCE"
CHANNEL_OTHER_BALANCE Channel = "OTHER_BALANCE"
CHANNEL_OTHER_BANKCARD Channel = "OTHER_BANKCARD"
)
type Status string
func (e Status) Ptr() *Status {
return &e
}
const (
STATUS_SUCCESS Status = "SUCCESS"
STATUS_CLOSED Status = "CLOSED"
STATUS_PROCESSING Status = "PROCESSING"
STATUS_ABNORMAL Status = "ABNORMAL"
)
type FundsAccount string
func (e FundsAccount) Ptr() *FundsAccount {
return &e
}
const (
FUNDSACCOUNT_UNSETTLED FundsAccount = "UNSETTLED"
FUNDSACCOUNT_AVAILABLE FundsAccount = "AVAILABLE"
FUNDSACCOUNT_UNAVAILABLE FundsAccount = "UNAVAILABLE"
FUNDSACCOUNT_OPERATION FundsAccount = "OPERATION"
FUNDSACCOUNT_BASIC FundsAccount = "BASIC"
FUNDSACCOUNT_ECNY_BASIC FundsAccount = "ECNY_BASIC"
)
type Amount struct {
Total *int64 `json:"total,omitempty"`
Refund *int64 `json:"refund,omitempty"`
From []FundsFromItem `json:"from,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
PayerRefund *int64 `json:"payer_refund,omitempty"`
SettlementRefund *int64 `json:"settlement_refund,omitempty"`
SettlementTotal *int64 `json:"settlement_total,omitempty"`
DiscountRefund *int64 `json:"discount_refund,omitempty"`
Currency *string `json:"currency,omitempty"`
RefundFee *int64 `json:"refund_fee,omitempty"`
}
type Promotion struct {
PromotionId *string `json:"promotion_id,omitempty"`
Scope *PromotionScope `json:"scope,omitempty"`
Type *PromotionType `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type FundsFromItem struct {
Account *Account `json:"account,omitempty"`
Amount *int64 `json:"amount,omitempty"`
}
type PromotionScope string
func (e PromotionScope) Ptr() *PromotionScope {
return &e
}
const (
PROMOTIONSCOPE_GLOBAL PromotionScope = "GLOBAL"
PROMOTIONSCOPE_SINGLE PromotionScope = "SINGLE"
)
type PromotionType string
func (e PromotionType) Ptr() *PromotionType {
return &e
}
const (
PROMOTIONTYPE_CASH PromotionType = "CASH"
PROMOTIONTYPE_NOCASH PromotionType = "NOCASH"
)
type Account string
func (e Account) Ptr() *Account {
return &e
}
const (
ACCOUNT_AVAILABLE Account = "AVAILABLE"
ACCOUNT_UNAVAILABLE Account = "UNAVAILABLE"
)

View File

@@ -0,0 +1,251 @@
package main
import (
"demo/wxpay_utility" // 引用微信支付工具库,参考 https://pay.weixin.qq.com/doc/v3/merchant/4015119334
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
func main() {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
config, err := wxpay_utility.CreateMchConfig(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem", // 微信支付公钥文件路径,本地文件路径
)
if err != nil {
fmt.Println(err)
return
}
request := &QueryByOutRefundNoRequest{
OutRefundNo: wxpay_utility.String("1217752501201407033233368018"),
}
response, err := QueryByOutRefundNo(config, request)
if err != nil {
fmt.Printf("请求失败: %+v\n", err)
// TODO: 请求失败,根据状态码执行不同的处理
return
}
// TODO: 请求成功,继续业务逻辑
fmt.Printf("请求成功: %+v\n", response)
}
// QueryByOutRefundNo 查询单笔退款(通过商户退款单号)
//
// 提交退款申请后推荐每间隔1分钟调用该接口查询一次退款状态若超过5分钟仍是退款处理中状态
// 建议开始逐步衰减查询频率(比如之后间隔5分钟、10分钟、20分钟、30分钟……查询一次)。
//
// 退款有一定延时零钱支付的订单退款一般5分钟内到账银行卡支付的订单退款一般1-3个工作日到账。
//
// 同一商户号查询退款频率限制为300qps如返回FREQUENCY_LIMITED频率限制报错可间隔1分钟再重试查询。
func QueryByOutRefundNo(config *wxpay_utility.MchConfig, request *QueryByOutRefundNoRequest) (response *Refund, err error) {
const (
host = "https://api.mch.weixin.qq.com"
method = "GET"
path = "/v3/refund/domestic/refunds/{out_refund_no}"
)
reqUrl, err := url.Parse(fmt.Sprintf("%s%s", host, path))
if err != nil {
return nil, err
}
reqUrl.Path = strings.Replace(reqUrl.Path, "{out_refund_no}", url.PathEscape(*request.OutRefundNo), -1)
query := reqUrl.Query()
reqUrl.RawQuery = query.Encode()
httpRequest, err := http.NewRequest(method, reqUrl.String(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Accept", "application/json")
httpRequest.Header.Set("Wechatpay-Serial", config.WechatPayPublicKeyId())
authorization, err := wxpay_utility.BuildAuthorization(config.MchId(), config.CertificateSerialNo(), config.PrivateKey(), method, reqUrl.RequestURI(), nil)
if err != nil {
return nil, err
}
httpRequest.Header.Set("Authorization", authorization)
client := &http.Client{}
httpResponse, err := client.Do(httpRequest)
if err != nil {
return nil, err
}
respBody, err := wxpay_utility.ExtractResponseBody(httpResponse)
if err != nil {
return nil, err
}
if httpResponse.StatusCode >= 200 && httpResponse.StatusCode < 300 {
// 2XX 成功,验证应答签名
err = wxpay_utility.ValidateResponse(
config.WechatPayPublicKeyId(),
config.WechatPayPublicKey(),
&httpResponse.Header,
respBody,
)
if err != nil {
return nil, err
}
response := &Refund{}
if err := json.Unmarshal(respBody, response); err != nil {
return nil, err
}
return response, nil
} else {
return nil, wxpay_utility.NewApiException(
httpResponse.StatusCode,
httpResponse.Header,
respBody,
)
}
}
type QueryByOutRefundNoRequest struct {
OutRefundNo *string `json:"out_refund_no,omitempty"`
}
func (o *QueryByOutRefundNoRequest) MarshalJSON() ([]byte, error) {
type Alias QueryByOutRefundNoRequest
a := &struct {
OutRefundNo *string `json:"out_refund_no,omitempty"`
*Alias
}{
// 序列化时移除非 Body 字段
OutRefundNo: nil,
Alias: (*Alias)(o),
}
return json.Marshal(a)
}
type Refund struct {
RefundId *string `json:"refund_id,omitempty"`
OutRefundNo *string `json:"out_refund_no,omitempty"`
TransactionId *string `json:"transaction_id,omitempty"`
OutTradeNo *string `json:"out_trade_no,omitempty"`
Channel *Channel `json:"channel,omitempty"`
UserReceivedAccount *string `json:"user_received_account,omitempty"`
SuccessTime *time.Time `json:"success_time,omitempty"`
CreateTime *time.Time `json:"create_time,omitempty"`
Status *Status `json:"status,omitempty"`
FundsAccount *FundsAccount `json:"funds_account,omitempty"`
Amount *Amount `json:"amount,omitempty"`
PromotionDetail []Promotion `json:"promotion_detail,omitempty"`
}
type Channel string
func (e Channel) Ptr() *Channel {
return &e
}
const (
CHANNEL_ORIGINAL Channel = "ORIGINAL"
CHANNEL_BALANCE Channel = "BALANCE"
CHANNEL_OTHER_BALANCE Channel = "OTHER_BALANCE"
CHANNEL_OTHER_BANKCARD Channel = "OTHER_BANKCARD"
)
type Status string
func (e Status) Ptr() *Status {
return &e
}
const (
STATUS_SUCCESS Status = "SUCCESS"
STATUS_CLOSED Status = "CLOSED"
STATUS_PROCESSING Status = "PROCESSING"
STATUS_ABNORMAL Status = "ABNORMAL"
)
type FundsAccount string
func (e FundsAccount) Ptr() *FundsAccount {
return &e
}
const (
FUNDSACCOUNT_UNSETTLED FundsAccount = "UNSETTLED"
FUNDSACCOUNT_AVAILABLE FundsAccount = "AVAILABLE"
FUNDSACCOUNT_UNAVAILABLE FundsAccount = "UNAVAILABLE"
FUNDSACCOUNT_OPERATION FundsAccount = "OPERATION"
FUNDSACCOUNT_BASIC FundsAccount = "BASIC"
FUNDSACCOUNT_ECNY_BASIC FundsAccount = "ECNY_BASIC"
)
type Amount struct {
Total *int64 `json:"total,omitempty"`
Refund *int64 `json:"refund,omitempty"`
From []FundsFromItem `json:"from,omitempty"`
PayerTotal *int64 `json:"payer_total,omitempty"`
PayerRefund *int64 `json:"payer_refund,omitempty"`
SettlementRefund *int64 `json:"settlement_refund,omitempty"`
SettlementTotal *int64 `json:"settlement_total,omitempty"`
DiscountRefund *int64 `json:"discount_refund,omitempty"`
Currency *string `json:"currency,omitempty"`
RefundFee *int64 `json:"refund_fee,omitempty"`
}
type Promotion struct {
PromotionId *string `json:"promotion_id,omitempty"`
Scope *PromotionScope `json:"scope,omitempty"`
Type *PromotionType `json:"type,omitempty"`
Amount *int64 `json:"amount,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
GoodsDetail []GoodsDetail `json:"goods_detail,omitempty"`
}
type FundsFromItem struct {
Account *Account `json:"account,omitempty"`
Amount *int64 `json:"amount,omitempty"`
}
type PromotionScope string
func (e PromotionScope) Ptr() *PromotionScope {
return &e
}
const (
PROMOTIONSCOPE_GLOBAL PromotionScope = "GLOBAL"
PROMOTIONSCOPE_SINGLE PromotionScope = "SINGLE"
)
type PromotionType string
func (e PromotionType) Ptr() *PromotionType {
return &e
}
const (
PROMOTIONTYPE_CASH PromotionType = "CASH"
PROMOTIONTYPE_NOCASH PromotionType = "NOCASH"
)
type GoodsDetail struct {
MerchantGoodsId *string `json:"merchant_goods_id,omitempty"`
WechatpayGoodsId *string `json:"wechatpay_goods_id,omitempty"`
GoodsName *string `json:"goods_name,omitempty"`
UnitPrice *int64 `json:"unit_price,omitempty"`
RefundAmount *int64 `json:"refund_amount,omitempty"`
RefundQuantity *int64 `json:"refund_quantity,omitempty"`
}
type Account string
func (e Account) Ptr() *Account {
return &e
}
const (
ACCOUNT_AVAILABLE Account = "AVAILABLE"
ACCOUNT_UNAVAILABLE Account = "UNAVAILABLE"
)

View File

@@ -0,0 +1,39 @@
# JSAPI调起支付
## 说明
商户通过JSAPI/小程序下单接口获取到发起支付的必要参数 `prepay_id`再通过微信浏览器内置对象方法WeixinJSBridge调起微信支付收银台。
## 示例代码
```javascript
function onBridgeReady() {
WeixinJSBridge.invoke('getBrandWCPayRequest', {
"appId": "wx2421b1c4370ec43b", //公众号ID由商户传入
"timeStamp": "1395712654", //时间戳自1970年以来的秒数
"nonceStr": "e61463f8efa94090b1f366cccfbbb444", //随机串
"package": "prepay_id=wx21201855730335ac86f8c43d1889123400",
"signType": "RSA", //微信签名方式
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg=="
},
function(res) {
if (res.err_msg == "get_brand_wcpay_request:ok") {
// 使用以上方式判断前端返回,微信团队郑重提示:
// res.err_msg将在用户支付成功后返回ok但并不保证它绝对可靠
// 商户需进一步调用后端查单确认支付结果。
}
});
}
if (typeof WeixinJSBridge == "undefined") {
if (document.addEventListener) {
document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
```
> **重要**`res.err_msg` 将在用户支付成功后返回 ok但并不保证它绝对可靠商户需进一步调用后端查单确认支付结果。

View File

@@ -0,0 +1,254 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* JSAPI下单
*/
public class JsapiPrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/pay/transactions/jsapi";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
JsapiPrepay client = new JsapiPrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
DirectAPIv3JsapiPrepayRequest request = new DirectAPIv3JsapiPrepayRequest();
request.appid = "wxd678efh567hg6787";
request.mchid = "1230000109";
request.description = "Image形象店-深圳腾大-QQ公仔";
request.outTradeNo = "1217752501201407033233368018";
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.attach = "自定义数据说明";
request.notifyUrl = " https://www.weixin.qq.com/wxpay/pay.php";
request.goodsTag = "WXG";
request.supportFapiao = false;
request.amount = new CommonAmountInfo();
request.amount.total = 100L;
request.amount.currency = "CNY";
request.payer = new JsapiReqPayerInfo();
request.payer.openid = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o";
request.detail = new CouponInfo();
request.detail.costPrice = 608800L;
request.detail.invoiceId = "微信123";
request.detail.goodsDetail = new ArrayList<>();
{
GoodsDetail goodsDetailItem = new GoodsDetail();
goodsDetailItem.merchantGoodsId = "1246464644";
goodsDetailItem.wechatpayGoodsId = "1001";
goodsDetailItem.goodsName = "iPhoneX 256G";
goodsDetailItem.quantity = 1L;
goodsDetailItem.unitPrice = 528800L;
request.detail.goodsDetail.add(goodsDetailItem);
};
request.sceneInfo = new CommonSceneInfo();
request.sceneInfo.payerClientIp = "14.23.150.211";
request.sceneInfo.deviceId = "013467007045764";
request.sceneInfo.storeInfo = new StoreInfo();
request.sceneInfo.storeInfo.id = "0001";
request.sceneInfo.storeInfo.name = "腾讯大厦分店";
request.sceneInfo.storeInfo.areaCode = "440305";
request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
request.settleInfo = new SettleInfo();
request.settleInfo.profitSharing = false;
try {
DirectAPIv3JsapiPrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3JsapiPrepayResponse run(DirectAPIv3JsapiPrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3JsapiPrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public JsapiPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class DirectAPIv3JsapiPrepayRequest {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("description")
public String description;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("attach")
public String attach;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("support_fapiao")
public Boolean supportFapiao;
@SerializedName("amount")
public CommonAmountInfo amount;
@SerializedName("payer")
public JsapiReqPayerInfo payer;
@SerializedName("detail")
public CouponInfo detail;
@SerializedName("scene_info")
public CommonSceneInfo sceneInfo;
@SerializedName("settle_info")
public SettleInfo settleInfo;
}
public static class DirectAPIv3JsapiPrepayResponse {
@SerializedName("prepay_id")
public String prepayId;
}
public static class CommonAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("currency")
public String currency;
}
public static class JsapiReqPayerInfo {
@SerializedName("openid")
public String openid;
}
public static class CouponInfo {
@SerializedName("cost_price")
public Long costPrice;
@SerializedName("invoice_id")
public String invoiceId;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class CommonSceneInfo {
@SerializedName("payer_client_ip")
public String payerClientIp;
@SerializedName("device_id")
public String deviceId;
@SerializedName("store_info")
public StoreInfo storeInfo;
}
public static class SettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
}
public static class StoreInfo {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("area_code")
public String areaCode;
@SerializedName("address")
public String address;
}
}

View File

@@ -0,0 +1,84 @@
# 退款结果回调通知
> 参考官方文档https://pay.weixin.qq.com/doc/v3/merchant/4012791865
## 回调描述
用户支付完成后,商户可根据需求发起退款。当退款单状态发生变更时(变更为退款成功/退款关闭/退款异常微信支付会通过POST的请求方式向商户预先设置的退款回调地址申请退款传入的 notify_url发送回调通知让商户知晓退款单的处理结果。
> **注意**商户侧对微信支付回调IP有防火墙策略限制的需要对微信回调IP段开通白名单详情参考回调处理逻辑注意事项。
## 回调报文格式
```json
{
"id": "EV-2018022511223320873",
"create_time": "2018-06-08T10:34:56+08:00",
"resource_type": "encrypt-resource",
"event_type": "REFUND.SUCCESS",
"summary": "退款成功",
"resource": {
"algorithm": "AEAD_AES_256_GCM",
"original_type": "refund",
"ciphertext": "...",
"nonce": "...",
"associated_data": ""
}
}
```
## 关键字段说明
| 字段 | 说明 |
|------|------|
| `event_type` | 退款事件类型:`REFUND.SUCCESS`(退款成功)/ `REFUND.ABNORMAL`(退款异常)/ `REFUND.CLOSED`(退款关闭) |
| `resource.original_type` | 固定为 `refund` |
| `resource.algorithm` | 加密算法,固定为 `AEAD_AES_256_GCM` |
| `resource.ciphertext` | 密文需使用商户APIv3密钥解密后得到退款详情 |
| `resource.associated_data` | 附加数据,解密时作为 AAD 参数 |
| `resource.nonce` | 随机串,解密时作为 Nonce 参数 |
## 解密后的退款详情resource.ciphertext 解密结果)
```json
{
"mchid": "1900000100",
"transaction_id": "1008450740201411110005820873",
"out_trade_no": "20150806125346",
"refund_id": "50200207182018070300011301001",
"out_refund_no": "7752501201407033233368018",
"refund_status": "SUCCESS",
"success_time": "2018-06-08T10:34:56+08:00",
"user_received_account": "招商银行信用卡0403",
"amount": {
"total": 999,
"refund": 999,
"payer_total": 999,
"payer_refund": 999
}
}
```
| 字段 | 说明 |
|------|------|
| `refund_status` | 退款状态:`SUCCESS`(成功)/ `CLOSED`(关闭)/ `PROCESSING`(处理中)/ `ABNORMAL`(异常) |
| `user_received_account` | 退款入账账户,如"招商银行信用卡0403"、"支付用户零钱"等 |
| `amount.total` | 原订单金额(分) |
| `amount.refund` | 退款金额(分) |
| `amount.payer_total` | 用户实际支付金额(分) |
| `amount.payer_refund` | 用户实际收到的退款金额(分) |
## 处理要求
1. 接收到回调后,先验证请求签名(使用微信支付公钥验签)
2. 使用商户APIv3密钥 + AEAD_AES_256_GCM 解密 `resource.ciphertext` 得到退款明文
3. 验签通过:返回 HTTP 200 或 204无需返回应答报文
4. 验签不通过:返回 HTTP 4XX/5XX + `{"code": "FAIL", "message": "失败"}`
5. 应答后再异步处理业务逻辑,避免超时
## 重试机制
若商户应答失败或超时5秒微信支付按以下频次重试
15s / 15s / 30s / 3m / 10m / 20m / 30m / 30m / 30m / 60m / 3h / 3h / 3h / 6h / 6h最多15次
> **重要**:商户系统不能仅依赖回调通知获取结果,需结合查询退款接口使用,避免遗漏或延迟问题。收到重复通知时请做好幂等处理并持续应答 200。

View File

@@ -0,0 +1,154 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 申请交易账单API
*
* 关键注意:
* 1. 次日10点后拉取API仅支持3个月内单日账单更早的需在商户平台下载。
* 2. 返回的是下载链接download_url需二次请求下载gzip压缩CSV
* 3. 账单金额单位为"元"与下单API的"分"不同,对账时注意转换。
*/
public class GetTradeBill {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/bill/tradebill";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
GetTradeBill client = new GetTradeBill(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
GetTradeBillRequest request = new GetTradeBillRequest();
request.billDate = "2019-06-11";
request.billType = BillType.ALL;
request.tarType = TarType.GZIP;
try {
QueryBillEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public QueryBillEntity run(GetTradeBillRequest request) {
String uri = PATH;
Map<String, Object> args = new HashMap<>();
args.put("bill_date", request.billDate);
args.put("bill_type", request.billType);
args.put("tar_type", request.tarType);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, QueryBillEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public GetTradeBill(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class GetTradeBillRequest {
@SerializedName("bill_date")
@Expose(serialize = false)
public String billDate;
@SerializedName("bill_type")
@Expose(serialize = false)
public BillType billType;
@SerializedName("tar_type")
@Expose(serialize = false)
public TarType tarType;
}
public static class QueryBillEntity {
@SerializedName("hash_type")
public HashType hashType;
@SerializedName("hash_value")
public String hashValue;
@SerializedName("download_url")
public String downloadUrl;
}
public enum BillType {
@SerializedName("ALL")
ALL,
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("REFUND")
REFUND
}
public enum TarType {
@SerializedName("GZIP")
GZIP
}
public enum HashType {
@SerializedName("SHA1")
SHA1
}
}

View File

@@ -0,0 +1,149 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 申请资金账单API
*/
public class GetFundFlowBill {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/bill/fundflowbill";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
GetFundFlowBill client = new GetFundFlowBill(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
GetFundFlowBillRequest request = new GetFundFlowBillRequest();
request.billDate = "2019-06-11";
request.accountType = FundFlowBillAccountType.BASIC;
request.tarType = TarType.GZIP;
try {
QueryBillEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public QueryBillEntity run(GetFundFlowBillRequest request) {
String uri = PATH;
Map<String, Object> args = new HashMap<>();
args.put("bill_date", request.billDate);
args.put("account_type", request.accountType);
args.put("tar_type", request.tarType);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, QueryBillEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public GetFundFlowBill(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class GetFundFlowBillRequest {
@SerializedName("bill_date")
@Expose(serialize = false)
public String billDate;
@SerializedName("account_type")
@Expose(serialize = false)
public FundFlowBillAccountType accountType;
@SerializedName("tar_type")
@Expose(serialize = false)
public TarType tarType;
}
public static class QueryBillEntity {
@SerializedName("hash_type")
public HashType hashType;
@SerializedName("hash_value")
public String hashValue;
@SerializedName("download_url")
public String downloadUrl;
}
public enum FundFlowBillAccountType {
@SerializedName("BASIC")
BASIC,
@SerializedName("OPERATION")
OPERATION,
@SerializedName("FEES")
FEES
}
public enum TarType {
@SerializedName("GZIP")
GZIP
}
public enum HashType {
@SerializedName("SHA1")
SHA1
}
}

View File

@@ -0,0 +1,167 @@
package com.java.demo;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import com.java.utils.WXPayUtility; // 引用微信支付工具库 参考:https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.ResponseBody;
import okhttp3.Response;
import java.net.URI;
import java.net.URISyntaxException;
import org.apache.commons.codec.digest.DigestUtils;
import java.util.zip.GZIPInputStream;
/**
* 下载账单
*
* 当商户调用申请交易账单/申请资金账单接口获取到下载账单链接download_url后
* 需按照V3接口规则生成签名然后请求下载账单链接download_url获取对应的账单文件。
*/
public class DownloadBill {
private static String METHOD = "GET";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
DownloadBill client = new DownloadBill(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
DownloadBillRequest request = new DownloadBillRequest();
request.downloadUrl = "https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx";
request.localFilePath = "downloaded_bill.csv";
request.expectedHashType = HashType.SHA1;
request.expectedHashValue = "79bb0f45fc4c42234a918000b2668d689e2bde04";
request.tarType = TarType.GZIP;
try {
client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println("File downloaded successfully! Local file path: " + request.localFilePath);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public void run(DownloadBillRequest request) {
Request.Builder reqBuilder = new Request.Builder().url(request.downloadUrl);
String uri = getPathQueryFromUrl(request.downloadUrl);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization",
WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
if (httpResponse.code() < 200 || httpResponse.code() > 300) {
String respBody = WXPayUtility.extractBody(httpResponse);
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
// 2XX 成功,流式下载文件
ResponseBody body = httpResponse.body();
if (body == null) {
throw new IOException("Response body is null");
}
// 读取流
try (InputStream inputStream = (request.tarType == DownloadBill.TarType.GZIP)
? new GZIPInputStream(body.byteStream())
: body.byteStream();
FileOutputStream outputStream = new FileOutputStream(request.localFilePath)) {
byte[] buffer = new byte[8096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
// 下载成功后校验文件SHA1
if (request.expectedHashType == HashType.SHA1) {
String sha1 = DigestUtils.sha1Hex(new FileInputStream(request.localFilePath));
if (!sha1.equals(request.expectedHashValue)) {
throw new IOException("SHA1 checksum mismatch");
}
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private String getPathQueryFromUrl(String url) {
try {
URI uri = new URI(url);
String path = uri.getRawPath(); // /v3/billdownload/file
String query = uri.getRawQuery(); // token=xxx&tartype=gzip
return (query == null || query.isEmpty()) ? path : path + "?" + query;
} catch (URISyntaxException e) {
e.printStackTrace();
return "";
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public DownloadBill(String mchid, String certificateSerialNo, String privateKeyFilePath,
String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public enum HashType {
@SerializedName("SHA1")
SHA1
}
public static class DownloadBillRequest {
@SerializedName("download_url")
@Expose(serialize = false)
public String downloadUrl;
@SerializedName("local_file_path")
@Expose(serialize = false)
public String localFilePath;
@SerializedName("expected_hash_type")
@Expose(serialize = false)
public HashType expectedHashType;
@SerializedName("expected_hash_value")
@Expose(serialize = false)
public String expectedHashValue;
@SerializedName("tar_type")
@Expose(serialize = false)
public TarType tarType;
}
public enum TarType {
@SerializedName("GZIP")
GZIP
}
}

View File

@@ -0,0 +1,87 @@
package com.java.utils;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
/**
* 微信支付 HTTP 客户端,封装了请求签名、发送、应答验签的完整流程。
* 依赖 WXPayUtility 提供的签名、验签、序列化等基础能力。
*/
public class WXPayClient {
private static final String HOST = "https://api.mch.weixin.qq.com";
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public WXPayClient(String mchid, String certificateSerialNo, String privateKeyFilePath,
String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
/**
* 发送 GET 请求,返回已验签的应答 Body
*/
public String sendGet(String uri) {
return sendRequest("GET", uri, null);
}
/**
* 发送 POST 请求,返回已验签的应答 Body
*/
public String sendPost(String uri, String reqBody) {
return sendRequest("POST", uri, reqBody);
}
/**
* 使用公钥加密敏感信息
*/
public String encrypt(String plainText) {
return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
}
private String sendRequest(String method, String uri, String reqBody) {
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(
mchid, certificateSerialNo, privateKey, method, uri, reqBody));
if (reqBody != null) {
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody body = RequestBody.create(
MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(method, body);
} else {
reqBuilder.method(method, null);
}
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(reqBuilder.build()).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
WXPayUtility.validateResponse(wechatPayPublicKeyId, wechatPayPublicKey,
httpResponse.headers(), respBody);
return respBody;
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
}

View File

@@ -0,0 +1,700 @@
package com.java.utils;
import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Map.Entry;
import okhttp3.Headers;
import okhttp3.Response;
import okio.BufferedSource;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.DateTimeException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.security.MessageDigest;
import java.io.InputStream;
import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.Security;
public class WXPayUtility {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.addSerializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.serialize();
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.addDeserializationExclusionStrategy(new ExclusionStrategy() {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.deserialize();
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
})
.create();
private static final char[] SYMBOLS =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final SecureRandom random = new SecureRandom();
public static String toJson(Object object) {
return gson.toJson(object);
}
public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
return gson.fromJson(json, classOfT);
}
private static String readKeyStringFromPath(String keyPath) {
try {
return new String(Files.readAllBytes(Paths.get(keyPath)), StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public static PrivateKey loadPrivateKeyFromString(String keyString) {
try {
keyString = keyString.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
return KeyFactory.getInstance("RSA").generatePrivate(
new PKCS8EncodedKeySpec(Base64.getDecoder().decode(keyString)));
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(e);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(e);
}
}
public static PrivateKey loadPrivateKeyFromPath(String keyPath) {
return loadPrivateKeyFromString(readKeyStringFromPath(keyPath));
}
public static PublicKey loadPublicKeyFromString(String keyString) {
try {
keyString = keyString.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
return KeyFactory.getInstance("RSA").generatePublic(
new X509EncodedKeySpec(Base64.getDecoder().decode(keyString)));
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(e);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(e);
}
}
public static PublicKey loadPublicKeyFromPath(String keyPath) {
return loadPublicKeyFromString(readKeyStringFromPath(keyPath));
}
public static String createNonce(int length) {
char[] buf = new char[length];
for (int i = 0; i < length; ++i) {
buf[i] = SYMBOLS[random.nextInt(SYMBOLS.length)];
}
return new String(buf);
}
public static String encrypt(PublicKey publicKey, String plaintext) {
final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
try {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)));
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("RSA encryption using an illegal publicKey", e);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new IllegalArgumentException("Plaintext is too long", e);
}
}
public static String rsaOaepDecrypt(PrivateKey privateKey, String ciphertext) {
final String transformation = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
try {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(ciphertext));
return new String(decryptedBytes, StandardCharsets.UTF_8);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
throw new IllegalArgumentException("The current Java environment does not support " + transformation, e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("RSA decryption using an illegal privateKey", e);
} catch (BadPaddingException | IllegalBlockSizeException e) {
throw new IllegalArgumentException("Ciphertext decryption failed", e);
}
}
public static String aesAeadDecrypt(byte[] key, byte[] associatedData, byte[] nonce,
byte[] ciphertext) {
final String transformation = "AES/GCM/NoPadding";
final String algorithm = "AES";
final int tagLengthBit = 128;
try {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(
Cipher.DECRYPT_MODE,
new SecretKeySpec(key, algorithm),
new GCMParameterSpec(tagLengthBit, nonce));
if (associatedData != null) {
cipher.updateAAD(associatedData);
}
return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
} catch (InvalidKeyException
| InvalidAlgorithmParameterException
| BadPaddingException
| IllegalBlockSizeException
| NoSuchAlgorithmException
| NoSuchPaddingException e) {
throw new IllegalArgumentException(String.format("AesAeadDecrypt with %s Failed",
transformation), e);
}
}
public static String sign(String message, String algorithm, PrivateKey privateKey) {
byte[] sign;
try {
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(message.getBytes(StandardCharsets.UTF_8));
sign = signature.sign();
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("The current Java environment does not support " + algorithm, e);
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(algorithm + " signature uses an illegal privateKey.", e);
} catch (SignatureException e) {
throw new RuntimeException("An error occurred during the sign process.", e);
}
return Base64.getEncoder().encodeToString(sign);
}
public static boolean verify(String message, String signature, String algorithm,
PublicKey publicKey) {
try {
Signature sign = Signature.getInstance(algorithm);
sign.initVerify(publicKey);
sign.update(message.getBytes(StandardCharsets.UTF_8));
return sign.verify(Base64.getDecoder().decode(signature));
} catch (SignatureException e) {
return false;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException("verify uses an illegal publickey.", e);
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("The current Java environment does not support" + algorithm, e);
}
}
public static String buildAuthorization(String mchid, String certificateSerialNo,
PrivateKey privateKey,
String method, String uri, String body) {
String nonce = createNonce(32);
long timestamp = Instant.now().getEpochSecond();
String message = String.format("%s\n%s\n%d\n%s\n%s\n", method, uri, timestamp, nonce,
body == null ? "" : body);
String signature = sign(message, "SHA256withRSA", privateKey);
return String.format(
"WECHATPAY2-SHA256-RSA2048 mchid=\"%s\",nonce_str=\"%s\",signature=\"%s\"," +
"timestamp=\"%d\",serial_no=\"%s\"",
mchid, nonce, signature, timestamp, certificateSerialNo);
}
private static String calculateHash(InputStream inputStream, String algorithm) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hashBytes = digest.digest();
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException(algorithm + " algorithm not available", e);
} catch (IOException e) {
throw new RuntimeException("Error reading from input stream", e);
}
}
public static String sha256(InputStream inputStream) {
return calculateHash(inputStream, "SHA-256");
}
public static String sha1(InputStream inputStream) {
return calculateHash(inputStream, "SHA-1");
}
public static String sm3(InputStream inputStream) {
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(new BouncyCastleProvider());
}
try {
SM3Digest digest = new SM3Digest();
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
digest.update(buffer, 0, bytesRead);
}
byte[] hashBytes = new byte[digest.getDigestSize()];
digest.doFinal(hashBytes, 0);
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
} catch (IOException e) {
throw new RuntimeException("Error reading from input stream", e);
}
}
public static String urlEncode(String content) {
try {
return URLEncoder.encode(content, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public static String urlEncode(Map<String, Object> params) {
if (params == null || params.isEmpty()) {
return "";
}
StringBuilder result = new StringBuilder();
for (Entry<String, Object> entry : params.entrySet()) {
if (entry.getValue() == null) {
continue;
}
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof List) {
List<?> list = (List<?>) entry.getValue();
for (Object temp : list) {
appendParam(result, key, temp);
}
} else {
appendParam(result, key, value);
}
}
return result.toString();
}
private static void appendParam(StringBuilder result, String key, Object value) {
if (result.length() > 0) {
result.append("&");
}
String valueString;
if (value instanceof String || value instanceof Number ||
value instanceof Boolean || value instanceof Enum) {
valueString = value.toString();
} else {
valueString = toJson(value);
}
result.append(key)
.append("=")
.append(urlEncode(valueString));
}
public static String extractBody(Response response) {
if (response.body() == null) {
return "";
}
try {
BufferedSource source = response.body().source();
return source.readUtf8();
} catch (IOException e) {
throw new RuntimeException(String.format("An error occurred during reading response body. " +
"Status: %d", response.code()), e);
}
}
public static void validateResponse(String wechatpayPublicKeyId, PublicKey wechatpayPublicKey,
Headers headers,
String body) {
String timestamp = headers.get("Wechatpay-Timestamp");
String requestId = headers.get("Request-ID");
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new IllegalArgumentException(
String.format("Validate response failed, timestamp[%s] is expired, request-id[%s]",
timestamp, requestId));
}
} catch (DateTimeException | NumberFormatException e) {
throw new IllegalArgumentException(
String.format("Validate response failed, timestamp[%s] is invalid, request-id[%s]",
timestamp, requestId));
}
String serialNumber = headers.get("Wechatpay-Serial");
if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
throw new IllegalArgumentException(
String.format("Validate response failed, Invalid Wechatpay-Serial, Local: %s, Remote: " +
"%s", wechatpayPublicKeyId, serialNumber));
}
String signature = headers.get("Wechatpay-Signature");
String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
body == null ? "" : body);
boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
if (!success) {
throw new IllegalArgumentException(
String.format("Validate response failed,the WechatPay signature is incorrect.%n"
+ "Request-ID[%s]\tresponseHeader[%s]\tresponseBody[%.1024s]",
headers.get("Request-ID"), headers, body));
}
}
public static void validateNotification(String wechatpayPublicKeyId,
PublicKey wechatpayPublicKey, Headers headers,
String body) {
String timestamp = headers.get("Wechatpay-Timestamp");
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
if (Duration.between(responseTime, Instant.now()).abs().toMinutes() >= 5) {
throw new IllegalArgumentException(
String.format("Validate notification failed, timestamp[%s] is expired", timestamp));
}
} catch (DateTimeException | NumberFormatException e) {
throw new IllegalArgumentException(
String.format("Validate notification failed, timestamp[%s] is invalid", timestamp));
}
String serialNumber = headers.get("Wechatpay-Serial");
if (!Objects.equals(serialNumber, wechatpayPublicKeyId)) {
throw new IllegalArgumentException(
String.format("Validate notification failed, Invalid Wechatpay-Serial, Local: %s, " +
"Remote: %s",
wechatpayPublicKeyId,
serialNumber));
}
String signature = headers.get("Wechatpay-Signature");
String message = String.format("%s\n%s\n%s\n", timestamp, headers.get("Wechatpay-Nonce"),
body == null ? "" : body);
boolean success = verify(message, signature, "SHA256withRSA", wechatpayPublicKey);
if (!success) {
throw new IllegalArgumentException(
String.format("Validate notification failed, WechatPay signature is incorrect.\n"
+ "responseHeader[%s]\tresponseBody[%.1024s]",
headers, body));
}
}
public static Notification parseNotification(String apiv3Key, String wechatpayPublicKeyId,
PublicKey wechatpayPublicKey, Headers headers,
String body) {
validateNotification(wechatpayPublicKeyId, wechatpayPublicKey, headers, body);
Notification notification = gson.fromJson(body, Notification.class);
notification.decrypt(apiv3Key);
return notification;
}
public static class ApiException extends RuntimeException {
private static final long serialVersionUID = 2261086748874802175L;
private final int statusCode;
private final String body;
private final Headers headers;
private final String errorCode;
private final String errorMessage;
public ApiException(int statusCode, String body, Headers headers) {
super(String.format("微信支付API访问失败StatusCode: [%s], Body: [%s], Headers: [%s]", statusCode,
body, headers));
this.statusCode = statusCode;
this.body = body;
this.headers = headers;
if (body != null && !body.isEmpty()) {
JsonElement code;
JsonElement message;
try {
JsonObject jsonObject = gson.fromJson(body, JsonObject.class);
code = jsonObject.get("code");
message = jsonObject.get("message");
} catch (JsonSyntaxException ignored) {
code = null;
message = null;
}
this.errorCode = code == null ? null : code.getAsString();
this.errorMessage = message == null ? null : message.getAsString();
} else {
this.errorCode = null;
this.errorMessage = null;
}
}
public int getStatusCode() {
return statusCode;
}
public String getBody() {
return body;
}
public Headers getHeaders() {
return headers;
}
public String getErrorCode() {
return errorCode;
}
public String getErrorMessage() {
return errorMessage;
}
}
public static class Notification {
@SerializedName("id")
private String id;
@SerializedName("create_time")
private String createTime;
@SerializedName("event_type")
private String eventType;
@SerializedName("resource_type")
private String resourceType;
@SerializedName("summary")
private String summary;
@SerializedName("resource")
private Resource resource;
private String plaintext;
public String getId() {
return id;
}
public String getCreateTime() {
return createTime;
}
public String getEventType() {
return eventType;
}
public String getResourceType() {
return resourceType;
}
public String getSummary() {
return summary;
}
public Resource getResource() {
return resource;
}
public String getPlaintext() {
return plaintext;
}
private void validate() {
if (resource == null) {
throw new IllegalArgumentException("Missing required field `resource` in notification");
}
resource.validate();
}
private void decrypt(String apiv3Key) {
validate();
plaintext = aesAeadDecrypt(
apiv3Key.getBytes(StandardCharsets.UTF_8),
resource.associatedData.getBytes(StandardCharsets.UTF_8),
resource.nonce.getBytes(StandardCharsets.UTF_8),
Base64.getDecoder().decode(resource.ciphertext)
);
}
public static class Resource {
@SerializedName("algorithm")
private String algorithm;
@SerializedName("ciphertext")
private String ciphertext;
@SerializedName("associated_data")
private String associatedData;
@SerializedName("nonce")
private String nonce;
@SerializedName("original_type")
private String originalType;
public String getAlgorithm() {
return algorithm;
}
public String getCiphertext() {
return ciphertext;
}
public String getAssociatedData() {
return associatedData;
}
public String getNonce() {
return nonce;
}
public String getOriginalType() {
return originalType;
}
private void validate() {
if (algorithm == null || algorithm.isEmpty()) {
throw new IllegalArgumentException("Missing required field `algorithm` in Notification" +
".Resource");
}
if (!Objects.equals(algorithm, "AEAD_AES_256_GCM")) {
throw new IllegalArgumentException(String.format("Unsupported `algorithm`[%s] in " +
"Notification.Resource", algorithm));
}
if (ciphertext == null || ciphertext.isEmpty()) {
throw new IllegalArgumentException("Missing required field `ciphertext` in Notification" +
".Resource");
}
if (associatedData == null || associatedData.isEmpty()) {
throw new IllegalArgumentException("Missing required field `associatedData` in " +
"Notification.Resource");
}
if (nonce == null || nonce.isEmpty()) {
throw new IllegalArgumentException("Missing required field `nonce` in Notification" +
".Resource");
}
if (originalType == null || originalType.isEmpty()) {
throw new IllegalArgumentException("Missing required field `originalType` in " +
"Notification.Resource");
}
}
}
}
public static String getContentTypeByFileName(String fileName) {
if (fileName == null || fileName.isEmpty()) {
return "application/octet-stream";
}
String extension = "";
int lastDotIndex = fileName.lastIndexOf('.');
if (lastDotIndex > 0 && lastDotIndex < fileName.length() - 1) {
extension = fileName.substring(lastDotIndex + 1).toLowerCase();
}
Map<String, String> contentTypeMap = new HashMap<>();
contentTypeMap.put("png", "image/png");
contentTypeMap.put("jpg", "image/jpeg");
contentTypeMap.put("jpeg", "image/jpeg");
contentTypeMap.put("gif", "image/gif");
contentTypeMap.put("bmp", "image/bmp");
contentTypeMap.put("webp", "image/webp");
contentTypeMap.put("svg", "image/svg+xml");
contentTypeMap.put("ico", "image/x-icon");
contentTypeMap.put("pdf", "application/pdf");
contentTypeMap.put("doc", "application/msword");
contentTypeMap.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
contentTypeMap.put("xls", "application/vnd.ms-excel");
contentTypeMap.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
contentTypeMap.put("ppt", "application/vnd.ms-powerpoint");
contentTypeMap.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation");
contentTypeMap.put("txt", "text/plain");
contentTypeMap.put("html", "text/html");
contentTypeMap.put("css", "text/css");
contentTypeMap.put("js", "application/javascript");
contentTypeMap.put("json", "application/json");
contentTypeMap.put("xml", "application/xml");
contentTypeMap.put("csv", "text/csv");
contentTypeMap.put("mp3", "audio/mpeg");
contentTypeMap.put("wav", "audio/wav");
contentTypeMap.put("mp4", "video/mp4");
contentTypeMap.put("avi", "video/x-msvideo");
contentTypeMap.put("mov", "video/quicktime");
contentTypeMap.put("zip", "application/zip");
contentTypeMap.put("rar", "application/x-rar-compressed");
contentTypeMap.put("7z", "application/x-7z-compressed");
return contentTypeMap.getOrDefault(extension, "application/octet-stream");
}
}

View File

@@ -0,0 +1,222 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合单下单-APP
*/
public class UnionAppPrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/combine-transactions/app";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionAppPrepay client = new UnionAppPrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionAPIv3AppPrepayRequest request = new UnionAPIv3AppPrepayRequest();
request.combineAppid = "wxd678efh567hg6787";
request.combineOutTradeNo = "20150806125345";
request.combineMchid = "1900000109";
request.sceneInfo = new UnionSceneInfo();
request.sceneInfo.deviceId = "POS1:1";
request.sceneInfo.payerClientIp = "14.17.22.32";
request.subOrders = new ArrayList<>();
{
UnionSubOrder subOrdersItem0 = new UnionSubOrder();
subOrdersItem0.mchid = "1230000109";
subOrdersItem0.outTradeNo = "20150806125346";
subOrdersItem0.amount = new UnionAmountInfo();
subOrdersItem0.amount.totalAmount = 10L;
subOrdersItem0.amount.currency = "CNY";
subOrdersItem0.attach = "深圳分店";
subOrdersItem0.description = "腾讯充值中心-QQ会员充值";
subOrdersItem0.detail = "买单费用";
subOrdersItem0.goodsTag = "WXG";
subOrdersItem0.settleInfo = new UnionSettleInfo();
subOrdersItem0.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem0);
UnionSubOrder subOrdersItem1 = new UnionSubOrder();
subOrdersItem1.mchid = "1230000119";
subOrdersItem1.outTradeNo = "20150806125347";
subOrdersItem1.amount = new UnionAmountInfo();
subOrdersItem1.amount.totalAmount = 10L;
subOrdersItem1.amount.currency = "CNY";
subOrdersItem1.attach = "广州分店";
subOrdersItem1.description = "腾讯充值中心-微信充值";
subOrdersItem1.detail = "买单费用";
subOrdersItem1.goodsTag = "WXG";
subOrdersItem1.settleInfo = new UnionSettleInfo();
subOrdersItem1.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem1);
};
request.combinePayerInfo = new UnionAppPayerInfo();
request.combinePayerInfo.openid = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o";
request.timeExpire = "2000-01-01T00:00:00+08:00";
request.notifyUrl = "https://yourapp.com/notify";
try {
UnionAPIv3AppPrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public UnionAPIv3AppPrepayResponse run(UnionAPIv3AppPrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, UnionAPIv3AppPrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionAppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionAPIv3AppPrepayRequest {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_out_trade_no")
public String combineOutTradeNo;
@SerializedName("combine_mchid")
public String combineMchid;
@SerializedName("scene_info")
public UnionSceneInfo sceneInfo;
@SerializedName("sub_orders")
public List<UnionSubOrder> subOrders = new ArrayList<UnionSubOrder>();
@SerializedName("combine_payer_info")
public UnionAppPayerInfo combinePayerInfo;
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("trade_scenario")
public String tradeScenario;
}
public static class UnionAPIv3AppPrepayResponse {
@SerializedName("prepay_id")
public String prepayId;
}
public static class UnionSceneInfo {
@SerializedName("device_id")
public String deviceId;
@SerializedName("payer_client_ip")
public String payerClientIp;
}
public static class UnionSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("amount")
public UnionAmountInfo amount;
@SerializedName("attach")
public String attach;
@SerializedName("description")
public String description;
@SerializedName("detail")
public String detail;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("settle_info")
public UnionSettleInfo settleInfo;
}
public static class UnionAppPayerInfo {
@SerializedName("openid")
public String openid;
}
public static class UnionAmountInfo {
@SerializedName("total_amount")
public Long totalAmount;
@SerializedName("currency")
public String currency;
}
public static class UnionSettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
}

View File

@@ -0,0 +1,124 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合单关单
*/
public class UnionClose {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionClose client = new UnionClose(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionCloseRequest request = new UnionCloseRequest();
request.combineOutTradeNo = "1217752501201407033233368018";
request.combineAppid = "wxd678efh567hg6787";
request.subOrders = new ArrayList<>();
{
UnionCloseSubOrder subOrdersItem = new UnionCloseSubOrder();
subOrdersItem.mchid = "1900000109";
subOrdersItem.outTradeNo = "20150806125346";
request.subOrders.add(subOrdersItem);
};
try {
client.run(request);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public void run(UnionCloseRequest request) {
String uri = PATH;
uri = uri.replace("{combine_out_trade_no}", WXPayUtility.urlEncode(request.combineOutTradeNo));
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
return;
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionClose(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionCloseRequest {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_out_trade_no")
@Expose(serialize = false)
public String combineOutTradeNo;
@SerializedName("sub_orders")
public List<UnionCloseSubOrder> subOrders = new ArrayList<UnionCloseSubOrder>();
}
public static class UnionCloseSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
}
}

View File

@@ -0,0 +1,230 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合单下单-H5
*/
public class UnionH5Prepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/combine-transactions/h5";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionH5Prepay client = new UnionH5Prepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionAPIv3H5PrepayRequest request = new UnionAPIv3H5PrepayRequest();
request.combineAppid = "wxd678efh567hg6787";
request.combineOutTradeNo = "1217752501201407033233368018";
request.combineMchid = "1230000109";
request.sceneInfo = new UnionH5SceneInfo();
request.sceneInfo.payerClientIp = "14.23.150.211";
request.sceneInfo.deviceId = "013467007045764";
request.sceneInfo.h5Info = new UnionH5Info();
request.sceneInfo.h5Info.type = "iOS";
request.sceneInfo.h5Info.appName = "王者荣耀";
request.sceneInfo.h5Info.appUrl = "https://pay.qq.com";
request.sceneInfo.h5Info.bundleId = "com.tencent.wzryiOS";
request.sceneInfo.h5Info.packageName = "com.tencent.tmgp.sgame";
request.subOrders = new ArrayList<>();
{
UnionCommonSubOrder subOrdersItem0 = new UnionCommonSubOrder();
subOrdersItem0.mchid = "1230000109";
subOrdersItem0.outTradeNo = "20150806125346";
subOrdersItem0.amount = new UnionAmountInfo();
subOrdersItem0.amount.totalAmount = 10L;
subOrdersItem0.amount.currency = "CNY";
subOrdersItem0.attach = "深圳分店";
subOrdersItem0.description = "腾讯充值中心-QQ会员充值";
subOrdersItem0.goodsTag = "WXG";
subOrdersItem0.settleInfo = new UnionSettleInfo();
subOrdersItem0.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem0);
UnionCommonSubOrder subOrdersItem1 = new UnionCommonSubOrder();
subOrdersItem1.mchid = "1230000119";
subOrdersItem1.outTradeNo = "20150806125347";
subOrdersItem1.amount = new UnionAmountInfo();
subOrdersItem1.amount.totalAmount = 10L;
subOrdersItem1.amount.currency = "CNY";
subOrdersItem1.attach = "广州分店";
subOrdersItem1.description = "腾讯充值中心-微信充值";
subOrdersItem1.goodsTag = "WXG";
subOrdersItem1.settleInfo = new UnionSettleInfo();
subOrdersItem1.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem1);
};
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.notifyUrl = "https://yourapp.com/notify";
try {
UnionAPIv3H5PrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public UnionAPIv3H5PrepayResponse run(UnionAPIv3H5PrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, UnionAPIv3H5PrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionH5Prepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionAPIv3H5PrepayRequest {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_out_trade_no")
public String combineOutTradeNo;
@SerializedName("combine_mchid")
public String combineMchid;
@SerializedName("scene_info")
public UnionH5SceneInfo sceneInfo;
@SerializedName("sub_orders")
public List<UnionCommonSubOrder> subOrders = new ArrayList<UnionCommonSubOrder>();
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("notify_url")
public String notifyUrl;
}
public static class UnionAPIv3H5PrepayResponse {
@SerializedName("h5_url")
public String h5Url;
}
public static class UnionH5SceneInfo {
@SerializedName("payer_client_ip")
public String payerClientIp;
@SerializedName("device_id")
public String deviceId;
@SerializedName("h5_info")
public UnionH5Info h5Info;
}
public static class UnionCommonSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("amount")
public UnionAmountInfo amount;
@SerializedName("attach")
public String attach;
@SerializedName("description")
public String description;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("settle_info")
public UnionSettleInfo settleInfo;
}
public static class UnionH5Info {
@SerializedName("type")
public String type;
@SerializedName("app_name")
public String appName;
@SerializedName("app_url")
public String appUrl;
@SerializedName("bundle_id")
public String bundleId;
@SerializedName("package_name")
public String packageName;
}
public static class UnionAmountInfo {
@SerializedName("total_amount")
public Long totalAmount;
@SerializedName("currency")
public String currency;
}
public static class UnionSettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
}

View File

@@ -0,0 +1,222 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合单下单-JSAPI
*/
public class UnionJsapiPrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/combine-transactions/jsapi";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionJsapiPrepay client = new UnionJsapiPrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionAPIv3JsapiPrepayRequest request = new UnionAPIv3JsapiPrepayRequest();
request.combineAppid = "wxd678efh567hg6787";
request.combineMchid = "1230000109";
request.combineOutTradeNo = "1217752501201407033233368018";
request.combinePayerInfo = new UnionPayerInfo();
request.combinePayerInfo.openid = "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o";
request.sceneInfo = new UnionSceneInfo();
request.sceneInfo.deviceId = "POS1:1";
request.sceneInfo.payerClientIp = "14.17.22.32";
request.subOrders = new ArrayList<>();
{
UnionSubOrder subOrdersItem0 = new UnionSubOrder();
subOrdersItem0.mchid = "1230000109";
subOrdersItem0.outTradeNo = "20150806125346";
subOrdersItem0.amount = new UnionAmountInfo();
subOrdersItem0.amount.totalAmount = 10L;
subOrdersItem0.amount.currency = "CNY";
subOrdersItem0.attach = "深圳分店";
subOrdersItem0.description = "腾讯充值中心-QQ会员充值";
subOrdersItem0.detail = "买单费用";
subOrdersItem0.goodsTag = "WXG";
subOrdersItem0.settleInfo = new UnionSettleInfo();
subOrdersItem0.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem0);
UnionSubOrder subOrdersItem1 = new UnionSubOrder();
subOrdersItem1.mchid = "1230000119";
subOrdersItem1.outTradeNo = "20150806125347";
subOrdersItem1.amount = new UnionAmountInfo();
subOrdersItem1.amount.totalAmount = 10L;
subOrdersItem1.amount.currency = "CNY";
subOrdersItem1.attach = "广州分店";
subOrdersItem1.description = "腾讯充值中心-微信充值";
subOrdersItem1.detail = "买单费用";
subOrdersItem1.goodsTag = "WXG";
subOrdersItem1.settleInfo = new UnionSettleInfo();
subOrdersItem1.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem1);
};
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.notifyUrl = "https://yourapp.com/notify";
try {
UnionAPIv3JsapiPrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public UnionAPIv3JsapiPrepayResponse run(UnionAPIv3JsapiPrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, UnionAPIv3JsapiPrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionJsapiPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionAPIv3JsapiPrepayRequest {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_mchid")
public String combineMchid;
@SerializedName("combine_out_trade_no")
public String combineOutTradeNo;
@SerializedName("combine_payer_info")
public UnionPayerInfo combinePayerInfo;
@SerializedName("scene_info")
public UnionSceneInfo sceneInfo;
@SerializedName("sub_orders")
public List<UnionSubOrder> subOrders = new ArrayList<UnionSubOrder>();
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("notify_url")
public String notifyUrl;
}
public static class UnionAPIv3JsapiPrepayResponse {
@SerializedName("prepay_id")
public String prepayId;
}
public static class UnionPayerInfo {
@SerializedName("openid")
public String openid;
@SerializedName("sub_openid")
public String subOpenid;
}
public static class UnionSceneInfo {
@SerializedName("device_id")
public String deviceId;
@SerializedName("payer_client_ip")
public String payerClientIp;
}
public static class UnionSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("amount")
public UnionAmountInfo amount;
@SerializedName("attach")
public String attach;
@SerializedName("description")
public String description;
@SerializedName("detail")
public String detail;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("settle_info")
public UnionSettleInfo settleInfo;
}
public static class UnionAmountInfo {
@SerializedName("total_amount")
public Long totalAmount;
@SerializedName("currency")
public String currency;
}
public static class UnionSettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
}

View File

@@ -0,0 +1,204 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 合单下单-NATIVE
*/
public class UnionNativePrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/combine-transactions/native";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionNativePrepay client = new UnionNativePrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionAPIv3NativePrepayRequest request = new UnionAPIv3NativePrepayRequest();
request.combineAppid = "wxd678efh567hg6787";
request.combineOutTradeNo = "20150806125346";
request.combineMchid = "1900000109";
request.sceneInfo = new UnionSceneInfo();
request.sceneInfo.deviceId = "POS1:1";
request.sceneInfo.payerClientIp = "14.17.22.32";
request.subOrders = new ArrayList<>();
{
UnionCommonSubOrder subOrdersItem0 = new UnionCommonSubOrder();
subOrdersItem0.mchid = "1230000109";
subOrdersItem0.outTradeNo = "20150806125346";
subOrdersItem0.amount = new UnionAmountInfo();
subOrdersItem0.amount.totalAmount = 10L;
subOrdersItem0.amount.currency = "CNY";
subOrdersItem0.attach = "深圳分店";
subOrdersItem0.description = "腾讯充值中心-QQ会员充值";
subOrdersItem0.goodsTag = "WXG";
subOrdersItem0.settleInfo = new UnionSettleInfo();
subOrdersItem0.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem0);
UnionCommonSubOrder subOrdersItem1 = new UnionCommonSubOrder();
subOrdersItem1.mchid = "1230000119";
subOrdersItem1.outTradeNo = "20150806125347";
subOrdersItem1.amount = new UnionAmountInfo();
subOrdersItem1.amount.totalAmount = 10L;
subOrdersItem1.amount.currency = "CNY";
subOrdersItem1.attach = "广州分店";
subOrdersItem1.description = "腾讯充值中心-微信充值";
subOrdersItem1.goodsTag = "WXG";
subOrdersItem1.settleInfo = new UnionSettleInfo();
subOrdersItem1.settleInfo.profitSharing = false;
request.subOrders.add(subOrdersItem1);
};
request.timeExpire = "2000-01-01T00:00:00+08:00";
request.notifyUrl = "https://yourapp.com/notify";
try {
UnionAPIv3NativePrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public UnionAPIv3NativePrepayResponse run(UnionAPIv3NativePrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, UnionAPIv3NativePrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionNativePrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionAPIv3NativePrepayRequest {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_out_trade_no")
public String combineOutTradeNo;
@SerializedName("combine_mchid")
public String combineMchid;
@SerializedName("scene_info")
public UnionSceneInfo sceneInfo;
@SerializedName("sub_orders")
public List<UnionCommonSubOrder> subOrders = new ArrayList<UnionCommonSubOrder>();
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("notify_url")
public String notifyUrl;
}
public static class UnionAPIv3NativePrepayResponse {
@SerializedName("code_url")
public String codeUrl;
}
public static class UnionSceneInfo {
@SerializedName("device_id")
public String deviceId;
@SerializedName("payer_client_ip")
public String payerClientIp;
}
public static class UnionCommonSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("amount")
public UnionAmountInfo amount;
@SerializedName("attach")
public String attach;
@SerializedName("description")
public String description;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("settle_info")
public UnionSettleInfo settleInfo;
}
public static class UnionAmountInfo {
@SerializedName("total_amount")
public Long totalAmount;
@SerializedName("currency")
public String currency;
}
public static class UnionSettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
}

View File

@@ -0,0 +1,242 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 商户订单号查询订单
*/
public class UnionQueryByOutTradeNo {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/combine-transactions/out-trade-no/{combine_out_trade_no}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnionQueryByOutTradeNo client = new UnionQueryByOutTradeNo(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnionQueryByOutTradeNoRequest request = new UnionQueryByOutTradeNoRequest();
request.combineOutTradeNo = "P20150806125346";
try {
UnionAPIv3UnionQueryResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public UnionAPIv3UnionQueryResponse run(UnionQueryByOutTradeNoRequest request) {
String uri = PATH;
uri = uri.replace("{combine_out_trade_no}", WXPayUtility.urlEncode(request.combineOutTradeNo));
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, UnionAPIv3UnionQueryResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnionQueryByOutTradeNo(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnionQueryByOutTradeNoRequest {
@SerializedName("combine_out_trade_no")
@Expose(serialize = false)
public String combineOutTradeNo;
}
public static class UnionAPIv3UnionQueryResponse {
@SerializedName("combine_appid")
public String combineAppid;
@SerializedName("combine_mchid")
public String combineMchid;
@SerializedName("combine_out_trade_no")
public String combineOutTradeNo;
@SerializedName("combine_payer_info")
public UnionCommRespPayerInfo combinePayerInfo;
@SerializedName("scene_info")
public UnionCommRespSceneInfo sceneInfo;
@SerializedName("sub_orders")
public List<UnionSubOrder> subOrders;
}
public static class UnionCommRespPayerInfo {
@SerializedName("openid")
public String openid;
}
public static class UnionCommRespSceneInfo {
@SerializedName("device_id")
public String deviceId;
}
public static class UnionSubOrder {
@SerializedName("mchid")
public String mchid;
@SerializedName("sub_mchid")
public String subMchid;
@SerializedName("sub_appid")
public String subAppid;
@SerializedName("sub_openid")
public String subOpenid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("trade_type")
public String tradeType;
@SerializedName("trade_state")
public String tradeState;
@SerializedName("bank_type")
public String bankType;
@SerializedName("attach")
public String attach;
@SerializedName("success_time")
public String successTime;
@SerializedName("amount")
public UnionCommRespAmountInfo amount;
@SerializedName("promotion_detail")
public List<UnionPromotionDetail> promotionDetail;
}
public static class UnionCommRespAmountInfo {
@SerializedName("total_amount")
public Long totalAmount;
@SerializedName("payer_amount")
public Long payerAmount;
@SerializedName("currency")
public String currency;
@SerializedName("payer_currency")
public String payerCurrency;
@SerializedName("settlement_rate")
public Long settlementRate;
}
public static class UnionPromotionDetail {
@SerializedName("coupon_id")
public String couponId;
@SerializedName("name")
public String name;
@SerializedName("scope")
public String scope;
@SerializedName("type")
public String type;
@SerializedName("amount")
public Long amount;
@SerializedName("stock_id")
public String stockId;
@SerializedName("wechatpay_contribute")
public Long wechatpayContribute;
@SerializedName("merchant_contribute")
public Long merchantContribute;
@SerializedName("other_contribute")
public Long otherContribute;
@SerializedName("currency")
public String currency;
@SerializedName("goods_detail")
public List<GoodsDetailInPromotion> goodsDetail;
}
public static class GoodsDetailInPromotion {
@SerializedName("goods_id")
public String goodsId;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("discount_amount")
public Long discountAmount;
@SerializedName("goods_remark")
public String goodsRemark;
}
}

View File

@@ -0,0 +1,118 @@
# 合单订单支付成功回调通知
## 回调描述
用户使用合单支付APP合单支付/H5合单支付/JSAPI合单支付/Native合单支付/小程序合单支付功能当用户成功支付订单后微信支付会通过POST的请求方式向商户预先设置的回调地址APP合单支付/H5合单支付/JSAPI合单支付/Native合单支付/小程序合单支付下单接口传入的notify_url发送回调通知让商户知晓用户已完成支付。
> **注意**商户侧对微信支付回调IP有防火墙策略限制的需要对微信回调IP段开通白名单否则会导致收不到回调微信支付回调被商户防火墙拦截详情参考回调处理逻辑注意事项。
## 回调报文格式
微信支付会通过POST的方式向回调地址发送回调报文回调通知的请求主体中会包含JSON格式的通知参数
```json
{
"id": "EV-2018022511223320873",
"create_time": "2015-05-20T13:29:35+08:00",
"resource_type": "encrypt-resource",
"event_type": "TRANSACTION.SUCCESS",
"summary": "支付成功",
"resource": {
"original_type": "transaction",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "",
"associated_data": "",
"nonce": ""
}
}
```
## 关键字段说明
| 字段 | 说明 |
|------|------|
| `event_type` | 事件类型,支付成功为 `TRANSACTION.SUCCESS` |
| `resource.algorithm` | 加密算法,固定为 `AEAD_AES_256_GCM` |
| `resource.ciphertext` | 密文需使用商户APIv3密钥解密后得到合单订单详情 |
| `resource.associated_data` | 附加数据,解密时作为 AAD 参数 |
| `resource.nonce` | 随机串,解密时作为 Nonce 参数 |
## 解密后的合单订单明文结构
> 与基础支付回调的关键区别:解密后返回的是 `sub_orders[]` 数组(含各子单状态),而非单个订单。
```json
{
"combine_appid": "wxd678efh567hg6787",
"combine_out_trade_no": "20150806125346",
"combine_mchid": "1900000109",
"scene_info": {
"device_id": "POS1:1"
},
"sub_orders": [
{
"mchid": "1900000109",
"trade_type": "JSAPI",
"trade_state": "SUCCESS",
"bank_type": "CMC",
"attach": "深圳分店",
"amount": {
"total_amount": 10,
"currency": "CNY",
"payer_amount": 10,
"payer_currency": "CNY"
},
"success_time": "2015-05-20T13:29:35.120+08:00",
"transaction_id": "1009660380201506130728806387",
"out_trade_no": "20150806125346"
},
{
"mchid": "1900000179",
"trade_type": "JSAPI",
"trade_state": "SUCCESS",
"bank_type": "CMC",
"attach": "广州分店",
"amount": {
"total_amount": 10,
"currency": "CNY",
"payer_amount": 10,
"payer_currency": "CNY"
},
"success_time": "2015-05-20T13:29:35.120+08:00",
"transaction_id": "1009660380201506130728452147",
"out_trade_no": "20150806124855"
}
],
"combine_payer_info": {
"openid": "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o"
}
}
```
## 解密后关键字段说明
| 字段 | 说明 |
|------|------|
| `combine_appid` | 合单发起方的 APPID |
| `combine_mchid` | 合单发起方的商户号 |
| `combine_out_trade_no` | 合单商户订单号 |
| `sub_orders[].mchid` | 子单商户号 |
| `sub_orders[].trade_type` | 交易类型:`JSAPI``NATIVE``APP``MWEB` |
| `sub_orders[].trade_state` | 交易状态:`SUCCESS`(支付成功)、`NOTPAY`(未支付)、`CLOSED`(已关闭) |
| `sub_orders[].transaction_id` | 子单微信支付订单号(退款时需用此字段) |
| `sub_orders[].out_trade_no` | 子单商户订单号 |
| `sub_orders[].amount.total_amount` | 子单金额,单位:分 |
| `sub_orders[].amount.payer_amount` | 用户实付金额(= total_amount - 代金券优惠) |
| `sub_orders[].success_time` | 支付完成时间rfc3339 格式 |
| `sub_orders[].bank_type` | 银行类型,非银行卡支付统一返回 `OTHERS` |
| `combine_payer_info.openid` | 实际支付用户在 `combine_appid` 下的 openid |
## 处理要求
1. 接收到回调后,先验证请求签名(使用微信支付公钥验签),验签机制与基础支付相同
2. 使用商户APIv3密钥 + AEAD_AES_256_GCM 解密 `resource.ciphertext` 得到合单订单明文
3. 验签通过后返回 HTTP 200 或 204无应答报文表示确认收到
4. 验签不通过返回 HTTP 5XX 或 4XX + `{"code": "FAIL", "message": "失败原因"}`
5. 若返回非200或超时5秒微信支付按策略重试15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h最多15次
6. 不可仅依赖回调获取支付结果,需结合**查询合单订单接口**使用
7. 收到重复回调通知时需做好幂等处理,持续应答 200

View File

@@ -0,0 +1,174 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 添加分账接收方
*/
public class AddReceiver {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/profitsharing/receivers/add";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
AddReceiver client = new AddReceiver(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
AddReceiverRequest request = new AddReceiverRequest();
request.appid = "wx8888888888888888";
request.type = ReceiverType.MERCHANT_ID;
request.account = "86693852";
request.name = client.encrypt("name");
request.relationType = ReceiverRelationType.STORE;
request.customRelation = "代理商";
try {
AddReceiverResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public AddReceiverResponse run(AddReceiverRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, AddReceiverResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public AddReceiver(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public String encrypt(String plainText) {
return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
}
public static class AddReceiverRequest {
@SerializedName("appid")
public String appid;
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
@SerializedName("name")
public String name;
@SerializedName("relation_type")
public ReceiverRelationType relationType;
@SerializedName("custom_relation")
public String customRelation;
}
public static class AddReceiverResponse {
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
@SerializedName("name")
public String name;
@SerializedName("relation_type")
public ReceiverRelationType relationType;
@SerializedName("custom_relation")
public String customRelation;
}
public enum ReceiverType {
@SerializedName("MERCHANT_ID")
MERCHANT_ID,
@SerializedName("PERSONAL_OPENID")
PERSONAL_OPENID
}
public enum ReceiverRelationType {
@SerializedName("STORE")
STORE,
@SerializedName("STAFF")
STAFF,
@SerializedName("STORE_OWNER")
STORE_OWNER,
@SerializedName("PARTNER")
PARTNER,
@SerializedName("HEADQUARTER")
HEADQUARTER,
@SerializedName("BRAND")
BRAND,
@SerializedName("DISTRIBUTOR")
DISTRIBUTOR,
@SerializedName("USER")
USER,
@SerializedName("SUPPLIER")
SUPPLIER,
@SerializedName("CUSTOM")
CUSTOM
}
}

View File

@@ -0,0 +1,198 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 请求分账
*/
public class CreateOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/profitsharing/orders";
public static void main(String[] args) {
CreateOrder client = new CreateOrder(
"19xxxxxxxx",
"1DDE55AD98Exxxxxxxxxx",
"/path/to/apiclient_key.pem",
"PUB_KEY_ID_xxxxxxxxxxxxx",
"/path/to/wxp_pub.pem"
);
CreateOrderRequest request = new CreateOrderRequest();
request.appid = "wx8888888888888888";
request.transactionId = "4208450740201411110007820472";
request.outOrderNo = "P20150806125346";
request.receivers = new ArrayList<>();
{
CreateOrderReceiver receiversItem = new CreateOrderReceiver();
receiversItem.type = "MERCHANT_ID";
receiversItem.account = "86693852";
receiversItem.name = client.encrypt("name");
receiversItem.amount = 888L;
receiversItem.description = "分给商户A";
request.receivers.add(receiversItem);
};
request.unfreezeUnsplit = true;
try {
OrdersEntity response = client.run(request);
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
e.printStackTrace();
}
}
public OrdersEntity run(CreateOrderRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
return WXPayUtility.fromJson(respBody, OrdersEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public CreateOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public String encrypt(String plainText) {
return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
}
public static class CreateOrderRequest {
@SerializedName("appid")
public String appid;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("receivers")
public List<CreateOrderReceiver> receivers = new ArrayList<CreateOrderReceiver>();
@SerializedName("unfreeze_unsplit")
public Boolean unfreezeUnsplit;
}
public static class OrdersEntity {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("order_id")
public String orderId;
@SerializedName("state")
public OrderStatus state;
@SerializedName("receivers")
public List<OrderReceiverDetail> receivers = new ArrayList<OrderReceiverDetail>();
}
public static class CreateOrderReceiver {
@SerializedName("type")
public String type;
@SerializedName("account")
public String account;
@SerializedName("name")
public String name;
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
}
public enum OrderStatus {
@SerializedName("PROCESSING") PROCESSING,
@SerializedName("FINISHED") FINISHED
}
public static class OrderReceiverDetail {
@SerializedName("amount") public Long amount;
@SerializedName("description") public String description;
@SerializedName("type") public ReceiverType type;
@SerializedName("account") public String account;
@SerializedName("result") public DetailStatus result;
@SerializedName("fail_reason") public DetailFailReason failReason;
@SerializedName("create_time") public String createTime;
@SerializedName("finish_time") public String finishTime;
@SerializedName("detail_id") public String detailId;
}
public enum ReceiverType {
@SerializedName("MERCHANT_ID") MERCHANT_ID,
@SerializedName("PERSONAL_OPENID") PERSONAL_OPENID
}
public enum DetailStatus {
@SerializedName("PENDING") PENDING,
@SerializedName("SUCCESS") SUCCESS,
@SerializedName("CLOSED") CLOSED
}
public enum DetailFailReason {
@SerializedName("ACCOUNT_ABNORMAL") ACCOUNT_ABNORMAL,
@SerializedName("NO_RELATION") NO_RELATION,
@SerializedName("RECEIVER_HIGH_RISK") RECEIVER_HIGH_RISK,
@SerializedName("RECEIVER_REAL_NAME_NOT_VERIFIED") RECEIVER_REAL_NAME_NOT_VERIFIED,
@SerializedName("NO_AUTH") NO_AUTH,
@SerializedName("RECEIVER_RECEIPT_LIMIT") RECEIVER_RECEIPT_LIMIT,
@SerializedName("PAYER_ACCOUNT_ABNORMAL") PAYER_ACCOUNT_ABNORMAL,
@SerializedName("INVALID_REQUEST") INVALID_REQUEST
}
}

View File

@@ -0,0 +1,180 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 请求分账回退
*/
public class CreateReturnOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/profitsharing/return-orders";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
CreateReturnOrder client = new CreateReturnOrder(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CreateReturnOrderRequest request = new CreateReturnOrderRequest();
request.orderId = "3008450740201411110007820472";
request.outOrderNo = "P20150806125346";
request.outReturnNo = "R20190516001";
request.returnMchid = "86693852";
request.amount = 10L;
request.description = "用户退款";
try {
ReturnOrdersEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public ReturnOrdersEntity run(CreateReturnOrderRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, ReturnOrdersEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public CreateReturnOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class CreateReturnOrderRequest {
@SerializedName("order_id")
public String orderId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("out_return_no")
public String outReturnNo;
@SerializedName("return_mchid")
public String returnMchid;
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
}
public static class ReturnOrdersEntity {
@SerializedName("order_id")
public String orderId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("out_return_no")
public String outReturnNo;
@SerializedName("return_id")
public String returnId;
@SerializedName("return_mchid")
public String returnMchid;
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
@SerializedName("result")
public ReturnOrderStatus result;
@SerializedName("fail_reason")
public ReturnOrderFailReason failReason;
@SerializedName("create_time")
public String createTime;
@SerializedName("finish_time")
public String finishTime;
}
public enum ReturnOrderStatus {
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("FAILED")
FAILED
}
public enum ReturnOrderFailReason {
@SerializedName("ACCOUNT_ABNORMAL")
ACCOUNT_ABNORMAL,
@SerializedName("BALANCE_NOT_ENOUGH")
BALANCE_NOT_ENOUGH,
@SerializedName("TIME_OUT_CLOSED")
TIME_OUT_CLOSED,
@SerializedName("PAYER_ACCOUNT_ABNORMAL")
PAYER_ACCOUNT_ABNORMAL,
@SerializedName("INVALID_REQUEST")
INVALID_REQUEST
}
}

View File

@@ -0,0 +1,126 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 删除分账接收方
*/
public class DeleteReceiver {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/profitsharing/receivers/delete";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
DeleteReceiver client = new DeleteReceiver(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
DeleteReceiverRequest request = new DeleteReceiverRequest();
request.appid = "wx8888888888888888";
request.type = ReceiverType.MERCHANT_ID;
request.account = "1900000109";
try {
DeleteReceiverResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DeleteReceiverResponse run(DeleteReceiverRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DeleteReceiverResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public DeleteReceiver(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class DeleteReceiverRequest {
@SerializedName("appid")
public String appid;
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
}
public static class DeleteReceiverResponse {
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
}
public enum ReceiverType {
@SerializedName("MERCHANT_ID")
MERCHANT_ID,
@SerializedName("PERSONAL_OPENID")
PERSONAL_OPENID
}
}

View File

@@ -0,0 +1,201 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 查询分账结果
*/
public class QueryOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/profitsharing/orders/{out_order_no}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryOrder client = new QueryOrder(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryOrderRequest request = new QueryOrderRequest();
request.outOrderNo = "P20150806125346";
request.transactionId = "4208450740201411110007820472";
try {
OrdersEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public OrdersEntity run(QueryOrderRequest request) {
String uri = PATH;
uri = uri.replace("{out_order_no}", WXPayUtility.urlEncode(request.outOrderNo));
Map<String, Object> args = new HashMap<>();
args.put("transaction_id", request.transactionId);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, OrdersEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryOrderRequest {
@SerializedName("transaction_id")
@Expose(serialize = false)
public String transactionId;
@SerializedName("out_order_no")
@Expose(serialize = false)
public String outOrderNo;
}
public static class OrdersEntity {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("order_id")
public String orderId;
@SerializedName("state")
public OrderStatus state;
@SerializedName("receivers")
public List<OrderReceiverDetail> receivers = new ArrayList<OrderReceiverDetail>();
}
public enum OrderStatus {
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("FINISHED")
FINISHED
}
public static class OrderReceiverDetail {
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
@SerializedName("result")
public DetailStatus result;
@SerializedName("fail_reason")
public DetailFailReason failReason;
@SerializedName("create_time")
public String createTime;
@SerializedName("finish_time")
public String finishTime;
@SerializedName("detail_id")
public String detailId;
}
public enum ReceiverType {
@SerializedName("MERCHANT_ID")
MERCHANT_ID,
@SerializedName("PERSONAL_OPENID")
PERSONAL_OPENID
}
public enum DetailStatus {
@SerializedName("PENDING")
PENDING,
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("CLOSED")
CLOSED
}
public enum DetailFailReason {
@SerializedName("ACCOUNT_ABNORMAL")
ACCOUNT_ABNORMAL,
@SerializedName("NO_RELATION")
NO_RELATION,
@SerializedName("RECEIVER_HIGH_RISK")
RECEIVER_HIGH_RISK,
@SerializedName("RECEIVER_REAL_NAME_NOT_VERIFIED")
RECEIVER_REAL_NAME_NOT_VERIFIED,
@SerializedName("NO_AUTH")
NO_AUTH,
@SerializedName("RECEIVER_RECEIPT_LIMIT")
RECEIVER_RECEIPT_LIMIT,
@SerializedName("PAYER_ACCOUNT_ABNORMAL")
PAYER_ACCOUNT_ABNORMAL,
@SerializedName("INVALID_REQUEST")
INVALID_REQUEST
}
}

View File

@@ -0,0 +1,110 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 查询剩余待分金额
*/
public class QueryOrderAmount {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/profitsharing/transactions/{transaction_id}/amounts";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryOrderAmount client = new QueryOrderAmount(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryOrderAmountRequest request = new QueryOrderAmountRequest();
request.transactionId = "4208450740201411110007820472";
try {
QueryOrderAmountResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public QueryOrderAmountResponse run(QueryOrderAmountRequest request) {
String uri = PATH;
uri = uri.replace("{transaction_id}", WXPayUtility.urlEncode(request.transactionId));
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, QueryOrderAmountResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryOrderAmount(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryOrderAmountRequest {
@SerializedName("transaction_id")
@Expose(serialize = false)
public String transactionId;
}
public static class QueryOrderAmountResponse {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("unsplit_amount")
public Long unsplitAmount;
}
}

View File

@@ -0,0 +1,170 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 查询分账回退结果
*/
public class QueryReturnOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/profitsharing/return-orders/{out_return_no}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryReturnOrder client = new QueryReturnOrder(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryReturnOrderRequest request = new QueryReturnOrderRequest();
request.outReturnNo = "R20190516001";
request.outOrderNo = "P20190806125346";
try {
ReturnOrdersEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public ReturnOrdersEntity run(QueryReturnOrderRequest request) {
String uri = PATH;
uri = uri.replace("{out_return_no}", WXPayUtility.urlEncode(request.outReturnNo));
Map<String, Object> args = new HashMap<>();
args.put("out_order_no", request.outOrderNo);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, ReturnOrdersEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryReturnOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryReturnOrderRequest {
@SerializedName("out_return_no")
@Expose(serialize = false)
public String outReturnNo;
@SerializedName("out_order_no")
@Expose(serialize = false)
public String outOrderNo;
}
public static class ReturnOrdersEntity {
@SerializedName("order_id")
public String orderId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("out_return_no")
public String outReturnNo;
@SerializedName("return_id")
public String returnId;
@SerializedName("return_mchid")
public String returnMchid;
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
@SerializedName("result")
public ReturnOrderStatus result;
@SerializedName("fail_reason")
public ReturnOrderFailReason failReason;
@SerializedName("create_time")
public String createTime;
@SerializedName("finish_time")
public String finishTime;
}
public enum ReturnOrderStatus {
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("FAILED")
FAILED
}
public enum ReturnOrderFailReason {
@SerializedName("ACCOUNT_ABNORMAL")
ACCOUNT_ABNORMAL,
@SerializedName("BALANCE_NOT_ENOUGH")
BALANCE_NOT_ENOUGH,
@SerializedName("TIME_OUT_CLOSED")
TIME_OUT_CLOSED,
@SerializedName("PAYER_ACCOUNT_ABNORMAL")
PAYER_ACCOUNT_ABNORMAL,
@SerializedName("INVALID_REQUEST")
INVALID_REQUEST
}
}

View File

@@ -0,0 +1,134 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 申请分账账单
*/
public class SplitBill {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/profitsharing/bills";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
SplitBill client = new SplitBill(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
SplitBillRequest request = new SplitBillRequest();
request.billDate = "2019-06-11";
request.tarType = SplitBillTarType.GZIP;
try {
SplitBillResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public SplitBillResponse run(SplitBillRequest request) {
String uri = PATH;
Map<String, Object> args = new HashMap<>();
args.put("bill_date", request.billDate);
args.put("tar_type", request.tarType);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, SplitBillResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public SplitBill(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class SplitBillRequest {
@SerializedName("bill_date")
@Expose(serialize = false)
public String billDate;
@SerializedName("tar_type")
@Expose(serialize = false)
public SplitBillTarType tarType;
}
public static class SplitBillResponse {
@SerializedName("hash_type")
public SplitBillHashType hashType;
@SerializedName("hash_value")
public String hashValue;
@SerializedName("download_url")
public String downloadUrl;
}
public enum SplitBillTarType {
@SerializedName("GZIP")
GZIP
}
public enum SplitBillHashType {
@SerializedName("SHA1")
SHA1
}
}

View File

@@ -0,0 +1,199 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 解冻剩余资金
*/
public class UnfreezeOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/profitsharing/orders/unfreeze";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
UnfreezeOrder client = new UnfreezeOrder(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
UnfreezeOrderRequest request = new UnfreezeOrderRequest();
request.transactionId = "4208450740201411110007820472";
request.outOrderNo = "P20150806125346";
request.description = "解冻全部剩余资金";
try {
OrdersEntity response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public OrdersEntity run(UnfreezeOrderRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, OrdersEntity.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public UnfreezeOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class UnfreezeOrderRequest {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("description")
public String description;
}
public static class OrdersEntity {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_order_no")
public String outOrderNo;
@SerializedName("order_id")
public String orderId;
@SerializedName("state")
public OrderStatus state;
@SerializedName("receivers")
public List<OrderReceiverDetail> receivers = new ArrayList<OrderReceiverDetail>();
}
public enum OrderStatus {
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("FINISHED")
FINISHED
}
public static class OrderReceiverDetail {
@SerializedName("amount")
public Long amount;
@SerializedName("description")
public String description;
@SerializedName("type")
public ReceiverType type;
@SerializedName("account")
public String account;
@SerializedName("result")
public DetailStatus result;
@SerializedName("fail_reason")
public DetailFailReason failReason;
@SerializedName("create_time")
public String createTime;
@SerializedName("finish_time")
public String finishTime;
@SerializedName("detail_id")
public String detailId;
}
public enum ReceiverType {
@SerializedName("MERCHANT_ID")
MERCHANT_ID,
@SerializedName("PERSONAL_OPENID")
PERSONAL_OPENID
}
public enum DetailStatus {
@SerializedName("PENDING")
PENDING,
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("CLOSED")
CLOSED
}
public enum DetailFailReason {
@SerializedName("ACCOUNT_ABNORMAL")
ACCOUNT_ABNORMAL,
@SerializedName("NO_RELATION")
NO_RELATION,
@SerializedName("RECEIVER_HIGH_RISK")
RECEIVER_HIGH_RISK,
@SerializedName("RECEIVER_REAL_NAME_NOT_VERIFIED")
RECEIVER_REAL_NAME_NOT_VERIFIED,
@SerializedName("NO_AUTH")
NO_AUTH,
@SerializedName("RECEIVER_RECEIPT_LIMIT")
RECEIVER_RECEIPT_LIMIT,
@SerializedName("PAYER_ACCOUNT_ABNORMAL")
PAYER_ACCOUNT_ABNORMAL,
@SerializedName("INVALID_REQUEST")
INVALID_REQUEST
}
}

View File

@@ -0,0 +1,75 @@
# APP调起支付
> 参考官方文档https://pay.weixin.qq.com/doc/v3/merchant/4013070351
## 说明
商户通过APP下单接口获取到发起支付的必要参数 `prepay_id`商户APP再通过 OpenSDK 的 `sendReq` 方法拉起微信支付。
**注意**:需严格遵循 OpenSDK 接入指引([Android](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/Android.html) / [iOS](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/iOS.html) / [鸿蒙](https://developers.weixin.qq.com/doc/oplatform/Mobile_App/Access_Guide/ohos.html)接入SDK以及配置开发环境。
## 请求参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `appId` | 是 | 下单时传入的应用ID |
| `partnerId` | 是 | 下单时传入的商户号 |
| `prepayId` | 是 | APP下单接口返回的 prepay_id有效期2小时 |
| `packageValue` | 是 | 固定值 `Sign=WXPay`iOS 中字段名为 `package` |
| `nonceStr` | 是 | 随机字符串不长于32位 |
| `timeStamp` | 是 | Unix 秒级时间戳 |
| `sign` | 是 | 使用 appId、timeStamp、nonceStr、prepayId + 商户API证书私钥生成的 RSA 签名 |
## iOS 示例代码
```objectivec
PayReq *request = [[[PayReq alloc] init] autorelease];
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.package = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
[WXApi sendReqrequest];
```
## Android 示例代码
```java
IWXAPI api;
PayReq request = new PayReq();
request.appId = "wxd930ea5d5a258f4f";
request.partnerId = "1900000109";
request.prepayId= "1101000000140415649af9fc314aa427",;
request.packageValue = "Sign=WXPay";
request.nonceStr= "1101000000140429eb40476f8896f4c9";
request.timeStamp= "1398746574";
request.sign= "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==";
api.sendReq(request);
```
## 鸿蒙示例代码
```typescript
IWXAPI api;
let req = new wxopensdk.PayReq
req.appId = 'wxd930ea5d5a258f4f'
req.partnerId = '1900000109'
req.prepayId = '1101000000140415649af9fc314aa427'
req.packageValue = 'Sign=WXPay'
req.nonceStr = '1101000000140429eb40476f8896f4c9'
req.timeStamp = '1398746574'
req.sign = 'oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg=='
api.sendReq(context: common.UIAbilityContext, req)
```
## 回调 errCode 说明
| errCode | 描述 | 商户APP处理方案 |
|---------|------|---------------|
| 0 | 成功 | 调用后端查单接口,订单已支付则展示支付成功页面 |
| -1 | 错误 | 可能原因签名错误、未注册AppID、AppID不匹配等 |
| -2 | 取消支付 | 用户取消支付返回App商户自行处理展示 |
> **重要**:前端回调不保证绝对可靠,不可只依赖前端回调判断订单支付状态,订单状态需以后端查询订单和支付成功回调通知为准。

View File

@@ -0,0 +1,244 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* App下单
*/
public class AppPrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/pay/transactions/app";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
AppPrepay client = new AppPrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CommonPrepayRequest request = new CommonPrepayRequest();
request.appid = "wxd678efh567hg6787";
request.mchid = "1230000109";
request.description = "Image形象店-深圳腾大-QQ公仔";
request.outTradeNo = "1217752501201407033233368018";
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.attach = "自定义数据说明";
request.notifyUrl = " https://www.weixin.qq.com/wxpay/pay.php";
request.goodsTag = "WXG";
request.supportFapiao = false;
request.amount = new CommonAmountInfo();
request.amount.total = 100L;
request.amount.currency = "CNY";
request.detail = new CouponInfo();
request.detail.costPrice = 608800L;
request.detail.invoiceId = "微信123";
request.detail.goodsDetail = new ArrayList<>();
{
GoodsDetail goodsDetailItem = new GoodsDetail();
goodsDetailItem.merchantGoodsId = "1246464644";
goodsDetailItem.wechatpayGoodsId = "1001";
goodsDetailItem.goodsName = "iPhoneX 256G";
goodsDetailItem.quantity = 1L;
goodsDetailItem.unitPrice = 528800L;
request.detail.goodsDetail.add(goodsDetailItem);
};
request.sceneInfo = new CommonSceneInfo();
request.sceneInfo.payerClientIp = "14.23.150.211";
request.sceneInfo.deviceId = "013467007045764";
request.sceneInfo.storeInfo = new StoreInfo();
request.sceneInfo.storeInfo.id = "0001";
request.sceneInfo.storeInfo.name = "腾讯大厦分店";
request.sceneInfo.storeInfo.areaCode = "440305";
request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
request.settleInfo = new SettleInfo();
request.settleInfo.profitSharing = false;
try {
DirectAPIv3AppPrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3AppPrepayResponse run(CommonPrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3AppPrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public AppPrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class CommonPrepayRequest {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("description")
public String description;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("attach")
public String attach;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("support_fapiao")
public Boolean supportFapiao;
@SerializedName("amount")
public CommonAmountInfo amount;
@SerializedName("detail")
public CouponInfo detail;
@SerializedName("scene_info")
public CommonSceneInfo sceneInfo;
@SerializedName("settle_info")
public SettleInfo settleInfo;
}
public static class DirectAPIv3AppPrepayResponse {
@SerializedName("prepay_id")
public String prepayId;
}
public static class CommonAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("currency")
public String currency;
}
public static class CouponInfo {
@SerializedName("cost_price")
public Long costPrice;
@SerializedName("invoice_id")
public String invoiceId;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class CommonSceneInfo {
@SerializedName("payer_client_ip")
public String payerClientIp;
@SerializedName("device_id")
public String deviceId;
@SerializedName("store_info")
public StoreInfo storeInfo;
}
public static class SettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
}
public static class StoreInfo {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("area_code")
public String areaCode;
@SerializedName("address")
public String address;
}
}

View File

@@ -0,0 +1,29 @@
# H5调起支付
> 参考官方文档https://pay.weixin.qq.com/doc/v3/merchant/4012791835
## 调起支付步骤
1. 商户通过H5下单接口获取到发起支付的必要参数 `h5_url`
2. 商户在配置了H5支付域名的网页中跳转 `h5_url`,调起微信支付收银台中间页
3. 微信支付收银台中间页会进行H5权限的校验和安全性检查校验通过后用户可正常支付
## 支付后返回指定页面
用户支付完成后默认返回发起支付的页面。如需返回指定页面,可在 `h5_url` 后拼接 `redirect_url` 参数:
```
h5_url=https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx20161110163838f231619da20804912345&package=1037687096&redirect_url=https%3A%2F%2Fwww.wechatpay.com.cn
```
**注意**
- `redirect_url` 的域名必须为商户配置的H5支付域名
- 需对 `redirect_url` 进行 urlencode 处理
## 返回商户页面后查单
用户点击"取消支付"或支付成功后点击"完成"时,会返回商户支付页面(或指定的 redirect_url 页面)。建议:
在回跳页面中增设"确认支付情况"按钮,用户点击后触发查单操作,确保用户能及时了解订单状态。
> **重要**H5支付不像JSAPI/APP有前端回调商户必须通过后端查单接口或支付成功回调通知来确认订单状态。

View File

@@ -0,0 +1,270 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* H5下单
*/
public class H5Prepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/pay/transactions/h5";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
H5Prepay client = new H5Prepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
DirectAPIv3H5PrepayRequest request = new DirectAPIv3H5PrepayRequest();
request.appid = "wxd678efh567hg6787";
request.mchid = "1230000109";
request.description = "Image形象店-深圳腾大-QQ公仔";
request.outTradeNo = "1217752501201407033233368018";
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.attach = "自定义数据说明";
request.notifyUrl = " https://www.weixin.qq.com/wxpay/pay.php";
request.goodsTag = "WXG";
request.supportFapiao = false;
request.amount = new CommonAmountInfo();
request.amount.total = 100L;
request.amount.currency = "CNY";
request.detail = new CouponInfo();
request.detail.costPrice = 608800L;
request.detail.invoiceId = "微信123";
request.detail.goodsDetail = new ArrayList<>();
{
GoodsDetail goodsDetailItem = new GoodsDetail();
goodsDetailItem.merchantGoodsId = "1246464644";
goodsDetailItem.wechatpayGoodsId = "1001";
goodsDetailItem.goodsName = "iPhoneX 256G";
goodsDetailItem.quantity = 1L;
goodsDetailItem.unitPrice = 528800L;
request.detail.goodsDetail.add(goodsDetailItem);
};
request.sceneInfo = new H5ReqSceneInfo();
request.sceneInfo.payerClientIp = "14.23.150.211";
request.sceneInfo.deviceId = "013467007045764";
request.sceneInfo.storeInfo = new StoreInfo();
request.sceneInfo.storeInfo.id = "0001";
request.sceneInfo.storeInfo.name = "腾讯大厦分店";
request.sceneInfo.storeInfo.areaCode = "440305";
request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
request.sceneInfo.h5Info = new H5Info();
request.sceneInfo.h5Info.type = "iOS";
request.sceneInfo.h5Info.appName = "王者荣耀";
request.sceneInfo.h5Info.appUrl = "https://pay.qq.com";
request.sceneInfo.h5Info.bundleId = "com.tencent.wzryiOS";
request.sceneInfo.h5Info.packageName = "com.tencent.tmgp.sgame";
request.settleInfo = new SettleInfo();
request.settleInfo.profitSharing = false;
try {
DirectAPIv3H5PrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3H5PrepayResponse run(DirectAPIv3H5PrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3H5PrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public H5Prepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class DirectAPIv3H5PrepayRequest {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("description")
public String description;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("attach")
public String attach;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("support_fapiao")
public Boolean supportFapiao;
@SerializedName("amount")
public CommonAmountInfo amount;
@SerializedName("detail")
public CouponInfo detail;
@SerializedName("scene_info")
public H5ReqSceneInfo sceneInfo;
@SerializedName("settle_info")
public SettleInfo settleInfo;
}
public static class DirectAPIv3H5PrepayResponse {
@SerializedName("h5_url")
public String h5Url;
}
public static class CommonAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("currency")
public String currency;
}
public static class CouponInfo {
@SerializedName("cost_price")
public Long costPrice;
@SerializedName("invoice_id")
public String invoiceId;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class H5ReqSceneInfo {
@SerializedName("payer_client_ip")
public String payerClientIp;
@SerializedName("device_id")
public String deviceId;
@SerializedName("store_info")
public StoreInfo storeInfo;
@SerializedName("h5_info")
public H5Info h5Info;
}
public static class SettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
}
public static class StoreInfo {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("area_code")
public String areaCode;
@SerializedName("address")
public String address;
}
public static class H5Info {
@SerializedName("type")
public String type;
@SerializedName("app_name")
public String appName;
@SerializedName("app_url")
public String appUrl;
@SerializedName("bundle_id")
public String bundleId;
@SerializedName("package_name")
public String packageName;
}
}

View File

@@ -0,0 +1,19 @@
# Native调起支付
> 参考官方文档https://pay.weixin.qq.com/doc/v3/merchant/4012791878
## 调起支付步骤
1. 通过 Native下单接口获取到发起支付的必要参数 `code_url`
2.`code_url` 链接转换为二维码图片后,展示给用户
3. 用户打开微信"扫一扫"功能,扫描二维码,进行 Native 支付
## 示例
`weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00` 生成二维码,展示给用户扫码即可。
## 注意事项
- Native 支付的 `code_url` 有效期为2小时超时需重新调用下单接口获取新的 `code_url`
- 二维码仅支持微信"扫一扫"功能扫描,不支持长按识别或相册识别
- 商户需通过后端查单接口或支付成功回调通知来确认订单状态,不能仅依赖用户告知

View File

@@ -0,0 +1,244 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Native下单
*/
public class NativePrepay {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/pay/transactions/native";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
NativePrepay client = new NativePrepay(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CommonPrepayRequest request = new CommonPrepayRequest();
request.appid = "wxd678efh567hg6787";
request.mchid = "1230000109";
request.description = "Image形象店-深圳腾大-QQ公仔";
request.outTradeNo = "1217752501201407033233368018";
request.timeExpire = "2018-06-08T10:34:56+08:00";
request.attach = "自定义数据说明";
request.notifyUrl = " https://www.weixin.qq.com/wxpay/pay.php";
request.goodsTag = "WXG";
request.supportFapiao = false;
request.amount = new CommonAmountInfo();
request.amount.total = 100L;
request.amount.currency = "CNY";
request.detail = new CouponInfo();
request.detail.costPrice = 608800L;
request.detail.invoiceId = "微信123";
request.detail.goodsDetail = new ArrayList<>();
{
GoodsDetail goodsDetailItem = new GoodsDetail();
goodsDetailItem.merchantGoodsId = "1246464644";
goodsDetailItem.wechatpayGoodsId = "1001";
goodsDetailItem.goodsName = "iPhoneX 256G";
goodsDetailItem.quantity = 1L;
goodsDetailItem.unitPrice = 528800L;
request.detail.goodsDetail.add(goodsDetailItem);
};
request.sceneInfo = new CommonSceneInfo();
request.sceneInfo.payerClientIp = "14.23.150.211";
request.sceneInfo.deviceId = "013467007045764";
request.sceneInfo.storeInfo = new StoreInfo();
request.sceneInfo.storeInfo.id = "0001";
request.sceneInfo.storeInfo.name = "腾讯大厦分店";
request.sceneInfo.storeInfo.areaCode = "440305";
request.sceneInfo.storeInfo.address = "广东省深圳市南山区科技中一道10000号";
request.settleInfo = new SettleInfo();
request.settleInfo.profitSharing = false;
try {
DirectAPIv3DirectNativePrepayResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3DirectNativePrepayResponse run(CommonPrepayRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3DirectNativePrepayResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public NativePrepay(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class CommonPrepayRequest {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("description")
public String description;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("time_expire")
public String timeExpire;
@SerializedName("attach")
public String attach;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("goods_tag")
public String goodsTag;
@SerializedName("support_fapiao")
public Boolean supportFapiao;
@SerializedName("amount")
public CommonAmountInfo amount;
@SerializedName("detail")
public CouponInfo detail;
@SerializedName("scene_info")
public CommonSceneInfo sceneInfo;
@SerializedName("settle_info")
public SettleInfo settleInfo;
}
public static class DirectAPIv3DirectNativePrepayResponse {
@SerializedName("code_url")
public String codeUrl;
}
public static class CommonAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("currency")
public String currency;
}
public static class CouponInfo {
@SerializedName("cost_price")
public Long costPrice;
@SerializedName("invoice_id")
public String invoiceId;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class CommonSceneInfo {
@SerializedName("payer_client_ip")
public String payerClientIp;
@SerializedName("device_id")
public String deviceId;
@SerializedName("store_info")
public StoreInfo storeInfo;
}
public static class SettleInfo {
@SerializedName("profit_sharing")
public Boolean profitSharing;
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
}
public static class StoreInfo {
@SerializedName("id")
public String id;
@SerializedName("name")
public String name;
@SerializedName("area_code")
public String areaCode;
@SerializedName("address")
public String address;
}
}

View File

@@ -0,0 +1,26 @@
# 小程序调起支付
## 说明
商户通过JSAPI/小程序下单接口获取到发起支付的必要参数 `prepay_id` 后,在小程序中通过 `wx.requestPayment` 调起微信支付收银台。
小程序和JSAPI共用同一个下单接口`/v3/pay/transactions/jsapi`),区别在于调起支付的前端方式不同:小程序用 `wx.requestPayment`JSAPI 用 `WeixinJSBridge`
## 示例代码
```javascript
wx.requestPayment(
{
"timeStamp": "1414561699",
"nonceStr": "5K8264ILTKCH16CQ2502SI8ZNMTM67VS",
"package": "prepay_id=wx201410272009395522657a690389285100",
"signType": "RSA",
"paySign": "oR9d8PuhnIc+YZ8cBHFCwfgpaK9gd7vaRvkYD7rthRAZ\/X+QBhcCYL21N7cHCTUxbQ+EAt6Uy+lwSN22f5YZvI45MLko8Pfso0jm46v5hqcVwrk6uddkGuT+Cdvu4WBqDzaDjnNa5UK3GfE1Wfl2gHxIIY5lLdUgWFts17D4WuolLLkiFZV+JSHMvH7eaLdT9N5GBovBwu5yYKUR7skR8Fu+LozcSqQixnlEZUfyE55feLOQTUYzLmR9pNtPbPsu6WVhbNHMS3Ss2+AehHvz+n64GDmXxbX++IOBvm2olHu3PsOUGRwhudhVf7UcGcunXt8cqNjKNqZLhLw4jq\/xDg==",
"success":function(res){},
"fail":function(res){},
"complete":function(res){}
}
)
```
> **重要**:前端回调不保证绝对可靠,商户需通过后端查单接口或支付成功回调通知来确认订单状态。

View File

@@ -0,0 +1,233 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 微信支付商户订单号查询订单
*/
public class QueryByOutTradeNo {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/pay/transactions/out-trade-no/{out_trade_no}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryByOutTradeNo client = new QueryByOutTradeNo(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryByOutTradeNoRequest request = new QueryByOutTradeNoRequest();
request.outTradeNo = "1217752501201407033233368018";
request.mchid = "1230000109";
try {
DirectAPIv3QueryResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3QueryResponse run(QueryByOutTradeNoRequest request) {
String uri = PATH;
uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.outTradeNo));
Map<String, Object> args = new HashMap<>();
args.put("mchid", request.mchid);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3QueryResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryByOutTradeNo(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryByOutTradeNoRequest {
@SerializedName("mchid")
@Expose(serialize = false)
public String mchid;
@SerializedName("out_trade_no")
@Expose(serialize = false)
public String outTradeNo;
}
public static class DirectAPIv3QueryResponse {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("trade_type")
public String tradeType;
@SerializedName("trade_state")
public String tradeState;
@SerializedName("trade_state_desc")
public String tradeStateDesc;
@SerializedName("bank_type")
public String bankType;
@SerializedName("attach")
public String attach;
@SerializedName("success_time")
public String successTime;
@SerializedName("payer")
public CommRespPayerInfo payer;
@SerializedName("amount")
public CommRespAmountInfo amount;
@SerializedName("scene_info")
public CommRespSceneInfo sceneInfo;
@SerializedName("promotion_detail")
public List<PromotionDetail> promotionDetail;
}
public static class CommRespPayerInfo {
@SerializedName("openid")
public String openid;
}
public static class CommRespAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("payer_total")
public Long payerTotal;
@SerializedName("currency")
public String currency;
@SerializedName("payer_currency")
public String payerCurrency;
}
public static class CommRespSceneInfo {
@SerializedName("device_id")
public String deviceId;
}
public static class PromotionDetail {
@SerializedName("coupon_id")
public String couponId;
@SerializedName("name")
public String name;
@SerializedName("scope")
public String scope;
@SerializedName("type")
public String type;
@SerializedName("amount")
public Long amount;
@SerializedName("stock_id")
public String stockId;
@SerializedName("wechatpay_contribute")
public Long wechatpayContribute;
@SerializedName("merchant_contribute")
public Long merchantContribute;
@SerializedName("other_contribute")
public Long otherContribute;
@SerializedName("currency")
public String currency;
@SerializedName("goods_detail")
public List<GoodsDetailInPromotion> goodsDetail;
}
public static class GoodsDetailInPromotion {
@SerializedName("goods_id")
public String goodsId;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("discount_amount")
public Long discountAmount;
@SerializedName("goods_remark")
public String goodsRemark;
}
}

View File

@@ -0,0 +1,233 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 微信支付订单号查询订单
*/
public class QueryByWxTradeNo {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/pay/transactions/id/{transaction_id}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryByWxTradeNo client = new QueryByWxTradeNo(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryByWxTradeNoRequest request = new QueryByWxTradeNoRequest();
request.transactionId = "1217752501201407033233368018";
request.mchid = "1230000109";
try {
DirectAPIv3QueryResponse response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public DirectAPIv3QueryResponse run(QueryByWxTradeNoRequest request) {
String uri = PATH;
uri = uri.replace("{transaction_id}", WXPayUtility.urlEncode(request.transactionId));
Map<String, Object> args = new HashMap<>();
args.put("mchid", request.mchid);
String queryString = WXPayUtility.urlEncode(args);
if (!queryString.isEmpty()) {
uri = uri + "?" + queryString;
}
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, DirectAPIv3QueryResponse.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryByWxTradeNo(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryByWxTradeNoRequest {
@SerializedName("mchid")
@Expose(serialize = false)
public String mchid;
@SerializedName("transaction_id")
@Expose(serialize = false)
public String transactionId;
}
public static class DirectAPIv3QueryResponse {
@SerializedName("appid")
public String appid;
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("trade_type")
public String tradeType;
@SerializedName("trade_state")
public String tradeState;
@SerializedName("trade_state_desc")
public String tradeStateDesc;
@SerializedName("bank_type")
public String bankType;
@SerializedName("attach")
public String attach;
@SerializedName("success_time")
public String successTime;
@SerializedName("payer")
public CommRespPayerInfo payer;
@SerializedName("amount")
public CommRespAmountInfo amount;
@SerializedName("scene_info")
public CommRespSceneInfo sceneInfo;
@SerializedName("promotion_detail")
public List<PromotionDetail> promotionDetail;
}
public static class CommRespPayerInfo {
@SerializedName("openid")
public String openid;
}
public static class CommRespAmountInfo {
@SerializedName("total")
public Long total;
@SerializedName("payer_total")
public Long payerTotal;
@SerializedName("currency")
public String currency;
@SerializedName("payer_currency")
public String payerCurrency;
}
public static class CommRespSceneInfo {
@SerializedName("device_id")
public String deviceId;
}
public static class PromotionDetail {
@SerializedName("coupon_id")
public String couponId;
@SerializedName("name")
public String name;
@SerializedName("scope")
public String scope;
@SerializedName("type")
public String type;
@SerializedName("amount")
public Long amount;
@SerializedName("stock_id")
public String stockId;
@SerializedName("wechatpay_contribute")
public Long wechatpayContribute;
@SerializedName("merchant_contribute")
public Long merchantContribute;
@SerializedName("other_contribute")
public Long otherContribute;
@SerializedName("currency")
public String currency;
@SerializedName("goods_detail")
public List<GoodsDetailInPromotion> goodsDetail;
}
public static class GoodsDetailInPromotion {
@SerializedName("goods_id")
public String goodsId;
@SerializedName("quantity")
public Long quantity;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("discount_amount")
public Long discountAmount;
@SerializedName("goods_remark")
public String goodsRemark;
}
}

View File

@@ -0,0 +1,110 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 关闭订单
*
* 未支付状态的订单,可在无需支付时调用此接口关闭订单。常见关单情况包括:
* 1. 用户在商户系统提交取消订单请求,商户需执行关单操作。
* 2. 订单超时未支付超出商户系统设定的可支付时间或下单时的time_expire支付截止时间商户需进行关单处理。
*/
public class CloseOrder {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/pay/transactions/out-trade-no/{out_trade_no}/close";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
CloseOrder client = new CloseOrder(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CloseOrderRequest request = new CloseOrderRequest();
request.outTradeNo = "1217752501201407033233368018";
request.mchid = "1230000109";
try {
client.run(request);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public void run(CloseOrderRequest request) {
String uri = PATH;
uri = uri.replace("{out_trade_no}", WXPayUtility.urlEncode(request.outTradeNo));
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
return;
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public CloseOrder(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class CloseOrderRequest {
@SerializedName("mchid")
public String mchid;
@SerializedName("out_trade_no")
@Expose(serialize = false)
public String outTradeNo;
}
}

View File

@@ -0,0 +1,314 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 发起异常退款
*/
public class CreateAbnormalRefund {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
CreateAbnormalRefund client = new CreateAbnormalRefund(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CreateAbnormalRefundRequest request = new CreateAbnormalRefundRequest();
request.refundId = "50000000382019052709732678859";
request.outRefundNo = "1217752501201407033233368018";
request.type = AbnormalReceiveType.MERCHANT_BANK_CARD;
request.bankType = "ICBC_DEBIT";
request.bankAccount = client.encrypt("bank_account");
request.realName = client.encrypt("real_name");
try {
Refund response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public Refund run(CreateAbnormalRefundRequest request) {
String uri = PATH;
uri = uri.replace("{refund_id}", WXPayUtility.urlEncode(request.refundId));
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, Refund.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public CreateAbnormalRefund(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public String encrypt(String plainText) {
return WXPayUtility.encrypt(this.wechatPayPublicKey, plainText);
}
public static class CreateAbnormalRefundRequest {
@SerializedName("refund_id")
@Expose(serialize = false)
public String refundId;
@SerializedName("out_refund_no")
public String outRefundNo;
@SerializedName("type")
public AbnormalReceiveType type;
@SerializedName("bank_type")
public String bankType;
@SerializedName("bank_account")
public String bankAccount;
@SerializedName("real_name")
public String realName;
}
public static class Refund {
@SerializedName("refund_id")
public String refundId;
@SerializedName("out_refund_no")
public String outRefundNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("channel")
public Channel channel;
@SerializedName("user_received_account")
public String userReceivedAccount;
@SerializedName("success_time")
public String successTime;
@SerializedName("create_time")
public String createTime;
@SerializedName("status")
public Status status;
@SerializedName("funds_account")
public FundsAccount fundsAccount;
@SerializedName("amount")
public Amount amount;
@SerializedName("promotion_detail")
public List<Promotion> promotionDetail;
}
public enum AbnormalReceiveType {
@SerializedName("USER_BANK_CARD")
USER_BANK_CARD,
@SerializedName("MERCHANT_BANK_CARD")
MERCHANT_BANK_CARD
}
public enum Channel {
@SerializedName("ORIGINAL")
ORIGINAL,
@SerializedName("BALANCE")
BALANCE,
@SerializedName("OTHER_BALANCE")
OTHER_BALANCE,
@SerializedName("OTHER_BANKCARD")
OTHER_BANKCARD
}
public enum Status {
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("CLOSED")
CLOSED,
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("ABNORMAL")
ABNORMAL
}
public enum FundsAccount {
@SerializedName("UNSETTLED")
UNSETTLED,
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE,
@SerializedName("OPERATION")
OPERATION,
@SerializedName("BASIC")
BASIC,
@SerializedName("ECNY_BASIC")
ECNY_BASIC
}
public static class Amount {
@SerializedName("total")
public Long total;
@SerializedName("refund")
public Long refund;
@SerializedName("from")
public List<FundsFromItem> from;
@SerializedName("payer_total")
public Long payerTotal;
@SerializedName("payer_refund")
public Long payerRefund;
@SerializedName("settlement_refund")
public Long settlementRefund;
@SerializedName("settlement_total")
public Long settlementTotal;
@SerializedName("discount_refund")
public Long discountRefund;
@SerializedName("currency")
public String currency;
@SerializedName("refund_fee")
public Long refundFee;
}
public static class Promotion {
@SerializedName("promotion_id")
public String promotionId;
@SerializedName("scope")
public PromotionScope scope;
@SerializedName("type")
public PromotionType type;
@SerializedName("amount")
public Long amount;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class FundsFromItem {
@SerializedName("account")
public Account account;
@SerializedName("amount")
public Long amount;
}
public enum PromotionScope {
@SerializedName("GLOBAL")
GLOBAL,
@SerializedName("SINGLE")
SINGLE
}
public enum PromotionType {
@SerializedName("CASH")
CASH,
@SerializedName("NOCASH")
NOCASH
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("refund_quantity")
public Long refundQuantity;
}
public enum Account {
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE
}
}

View File

@@ -0,0 +1,360 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 退款申请
*
* 支付成功后1年内可通过此接口将款项全部或部分原路退还给用户也可在商户平台手动操作
*
* 关键注意:
* 1. 一笔订单最多50次部分退款重试必须用原 out_refund_no否则会重复退款。
* 2. 接口返回成功仅表示受理成功,实际结果以退款回调通知或查询退款接口为准。
* 3. 原路退还银行卡1-3个工作日到账零钱即时到账。
* 4. 有代金券的订单部分退款时,退给用户 = 退款金额 × (实付 ÷ 总额),四舍五入。
* 5. 有分账的订单,需确保可用余额充足;部分分账未解冻时需先调"完结分账"。
*/
public class CreateRefund {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "POST";
private static String PATH = "/v3/refund/domestic/refunds";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
CreateRefund client = new CreateRefund(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
CreateRequest request = new CreateRequest();
request.transactionId = "1217752501201407033233368018";
request.outTradeNo = "1217752501201407033233368018";
request.outRefundNo = "1217752501201407033233368018";
request.reason = "商品已售完";
request.notifyUrl = "https://weixin.qq.com";
request.fundsAccount = ReqFundsAccount.AVAILABLE;
request.amount = new AmountReq();
request.amount.refund = 888L;
request.amount.from = new ArrayList<>();
{
FundsFromItem fromItem = new FundsFromItem();
fromItem.account = Account.AVAILABLE;
fromItem.amount = 444L;
request.amount.from.add(fromItem);
};
request.amount.total = 888L;
request.amount.currency = "CNY";
request.goodsDetail = new ArrayList<>();
{
GoodsDetail goodsDetailItem = new GoodsDetail();
goodsDetailItem.merchantGoodsId = "1217752501201407033233368018";
goodsDetailItem.wechatpayGoodsId = "1001";
goodsDetailItem.goodsName = "iPhone6s 16G";
goodsDetailItem.unitPrice = 528800L;
goodsDetailItem.refundAmount = 528800L;
goodsDetailItem.refundQuantity = 1L;
request.goodsDetail.add(goodsDetailItem);
};
try {
Refund response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public Refund run(CreateRequest request) {
String uri = PATH;
String reqBody = WXPayUtility.toJson(request);
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo,privateKey, METHOD, uri, reqBody));
reqBuilder.addHeader("Content-Type", "application/json");
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), reqBody);
reqBuilder.method(METHOD, requestBody);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, Refund.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public CreateRefund(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class CreateRequest {
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("out_refund_no")
public String outRefundNo;
@SerializedName("reason")
public String reason;
@SerializedName("notify_url")
public String notifyUrl;
@SerializedName("funds_account")
public ReqFundsAccount fundsAccount;
@SerializedName("amount")
public AmountReq amount;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class Refund {
@SerializedName("refund_id")
public String refundId;
@SerializedName("out_refund_no")
public String outRefundNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("channel")
public Channel channel;
@SerializedName("user_received_account")
public String userReceivedAccount;
@SerializedName("success_time")
public String successTime;
@SerializedName("create_time")
public String createTime;
@SerializedName("status")
public Status status;
@SerializedName("funds_account")
public FundsAccount fundsAccount;
@SerializedName("amount")
public Amount amount;
@SerializedName("promotion_detail")
public List<Promotion> promotionDetail;
}
public enum ReqFundsAccount {
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNSETTLED")
UNSETTLED
}
public static class AmountReq {
@SerializedName("refund")
public Long refund;
@SerializedName("from")
public List<FundsFromItem> from;
@SerializedName("total")
public Long total;
@SerializedName("currency")
public String currency;
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("refund_quantity")
public Long refundQuantity;
}
public enum Channel {
@SerializedName("ORIGINAL")
ORIGINAL,
@SerializedName("BALANCE")
BALANCE,
@SerializedName("OTHER_BALANCE")
OTHER_BALANCE,
@SerializedName("OTHER_BANKCARD")
OTHER_BANKCARD
}
public enum Status {
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("CLOSED")
CLOSED,
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("ABNORMAL")
ABNORMAL
}
public enum FundsAccount {
@SerializedName("UNSETTLED")
UNSETTLED,
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE,
@SerializedName("OPERATION")
OPERATION,
@SerializedName("BASIC")
BASIC,
@SerializedName("ECNY_BASIC")
ECNY_BASIC
}
public static class Amount {
@SerializedName("total")
public Long total;
@SerializedName("refund")
public Long refund;
@SerializedName("from")
public List<FundsFromItem> from;
@SerializedName("payer_total")
public Long payerTotal;
@SerializedName("payer_refund")
public Long payerRefund;
@SerializedName("settlement_refund")
public Long settlementRefund;
@SerializedName("settlement_total")
public Long settlementTotal;
@SerializedName("discount_refund")
public Long discountRefund;
@SerializedName("currency")
public String currency;
@SerializedName("refund_fee")
public Long refundFee;
}
public static class Promotion {
@SerializedName("promotion_id")
public String promotionId;
@SerializedName("scope")
public PromotionScope scope;
@SerializedName("type")
public PromotionType type;
@SerializedName("amount")
public Long amount;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class FundsFromItem {
@SerializedName("account")
public Account account;
@SerializedName("amount")
public Long amount;
}
public enum PromotionScope {
@SerializedName("GLOBAL")
GLOBAL,
@SerializedName("SINGLE")
SINGLE
}
public enum PromotionType {
@SerializedName("CASH")
CASH,
@SerializedName("NOCASH")
NOCASH
}
public enum Account {
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE
}
}

View File

@@ -0,0 +1,286 @@
package com.java.demo;
import com.java.utils.WXPayUtility; // 引用微信支付工具库参考https://pay.weixin.qq.com/doc/v3/merchant/4014931831
import com.google.gson.annotations.SerializedName;
import com.google.gson.annotations.Expose;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 查询单笔退款(通过商户退款单号)
*
* 提交退款申请后推荐每间隔1分钟调用该接口查询一次退款状态若超过5分钟仍是退款处理中状态
* 建议开始逐步衰减查询频率(比如之后间隔5分钟、10分钟、20分钟、30分钟……查询一次)。
*
* 退款有一定延时零钱支付的订单退款一般5分钟内到账银行卡支付的订单退款一般1-3个工作日到账。
*
* 同一商户号查询退款频率限制为300qps如返回FREQUENCY_LIMITED频率限制报错可间隔1分钟再重试查询。
*/
public class QueryByOutRefundNo {
private static String HOST = "https://api.mch.weixin.qq.com";
private static String METHOD = "GET";
private static String PATH = "/v3/refund/domestic/refunds/{out_refund_no}";
public static void main(String[] args) {
// TODO: 请准备商户开发必要参数参考https://pay.weixin.qq.com/doc/v3/merchant/4013070756
QueryByOutRefundNo client = new QueryByOutRefundNo(
"19xxxxxxxx", // 商户号,是由微信支付系统生成并分配给每个商户的唯一标识符,商户号获取方式参考 https://pay.weixin.qq.com/doc/v3/merchant/4013070756
"1DDE55AD98Exxxxxxxxxx", // 商户API证书序列号如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013053053
"/path/to/apiclient_key.pem", // 商户API证书私钥文件路径本地文件路径
"PUB_KEY_ID_xxxxxxxxxxxxx", // 微信支付公钥ID如何获取请参考 https://pay.weixin.qq.com/doc/v3/merchant/4013038816
"/path/to/wxp_pub.pem" // 微信支付公钥文件路径,本地文件路径
);
QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
request.outRefundNo = "1217752501201407033233368018";
try {
Refund response = client.run(request);
// TODO: 请求成功,继续业务逻辑
System.out.println(response);
} catch (WXPayUtility.ApiException e) {
// TODO: 请求失败,根据状态码执行不同的逻辑
e.printStackTrace();
}
}
public Refund run(QueryByOutRefundNoRequest request) {
String uri = PATH;
uri = uri.replace("{out_refund_no}", WXPayUtility.urlEncode(request.outRefundNo));
Request.Builder reqBuilder = new Request.Builder().url(HOST + uri);
reqBuilder.addHeader("Accept", "application/json");
reqBuilder.addHeader("Wechatpay-Serial", wechatPayPublicKeyId);
reqBuilder.addHeader("Authorization", WXPayUtility.buildAuthorization(mchid, certificateSerialNo, privateKey, METHOD, uri, null));
reqBuilder.method(METHOD, null);
Request httpRequest = reqBuilder.build();
// 发送HTTP请求
OkHttpClient client = new OkHttpClient.Builder().build();
try (Response httpResponse = client.newCall(httpRequest).execute()) {
String respBody = WXPayUtility.extractBody(httpResponse);
if (httpResponse.code() >= 200 && httpResponse.code() < 300) {
// 2XX 成功,验证应答签名
WXPayUtility.validateResponse(this.wechatPayPublicKeyId, this.wechatPayPublicKey,
httpResponse.headers(), respBody);
// 从HTTP应答报文构建返回数据
return WXPayUtility.fromJson(respBody, Refund.class);
} else {
throw new WXPayUtility.ApiException(httpResponse.code(), respBody, httpResponse.headers());
}
} catch (IOException e) {
throw new UncheckedIOException("Sending request to " + uri + " failed.", e);
}
}
private final String mchid;
private final String certificateSerialNo;
private final PrivateKey privateKey;
private final String wechatPayPublicKeyId;
private final PublicKey wechatPayPublicKey;
public QueryByOutRefundNo(String mchid, String certificateSerialNo, String privateKeyFilePath, String wechatPayPublicKeyId, String wechatPayPublicKeyFilePath) {
this.mchid = mchid;
this.certificateSerialNo = certificateSerialNo;
this.privateKey = WXPayUtility.loadPrivateKeyFromPath(privateKeyFilePath);
this.wechatPayPublicKeyId = wechatPayPublicKeyId;
this.wechatPayPublicKey = WXPayUtility.loadPublicKeyFromPath(wechatPayPublicKeyFilePath);
}
public static class QueryByOutRefundNoRequest {
@SerializedName("out_refund_no")
@Expose(serialize = false)
public String outRefundNo;
}
public static class Refund {
@SerializedName("refund_id")
public String refundId;
@SerializedName("out_refund_no")
public String outRefundNo;
@SerializedName("transaction_id")
public String transactionId;
@SerializedName("out_trade_no")
public String outTradeNo;
@SerializedName("channel")
public Channel channel;
@SerializedName("user_received_account")
public String userReceivedAccount;
@SerializedName("success_time")
public String successTime;
@SerializedName("create_time")
public String createTime;
@SerializedName("status")
public Status status;
@SerializedName("funds_account")
public FundsAccount fundsAccount;
@SerializedName("amount")
public Amount amount;
@SerializedName("promotion_detail")
public List<Promotion> promotionDetail;
}
public enum Channel {
@SerializedName("ORIGINAL")
ORIGINAL,
@SerializedName("BALANCE")
BALANCE,
@SerializedName("OTHER_BALANCE")
OTHER_BALANCE,
@SerializedName("OTHER_BANKCARD")
OTHER_BANKCARD
}
public enum Status {
@SerializedName("SUCCESS")
SUCCESS,
@SerializedName("CLOSED")
CLOSED,
@SerializedName("PROCESSING")
PROCESSING,
@SerializedName("ABNORMAL")
ABNORMAL
}
public enum FundsAccount {
@SerializedName("UNSETTLED")
UNSETTLED,
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE,
@SerializedName("OPERATION")
OPERATION,
@SerializedName("BASIC")
BASIC,
@SerializedName("ECNY_BASIC")
ECNY_BASIC
}
public static class Amount {
@SerializedName("total")
public Long total;
@SerializedName("refund")
public Long refund;
@SerializedName("from")
public List<FundsFromItem> from;
@SerializedName("payer_total")
public Long payerTotal;
@SerializedName("payer_refund")
public Long payerRefund;
@SerializedName("settlement_refund")
public Long settlementRefund;
@SerializedName("settlement_total")
public Long settlementTotal;
@SerializedName("discount_refund")
public Long discountRefund;
@SerializedName("currency")
public String currency;
@SerializedName("refund_fee")
public Long refundFee;
}
public static class Promotion {
@SerializedName("promotion_id")
public String promotionId;
@SerializedName("scope")
public PromotionScope scope;
@SerializedName("type")
public PromotionType type;
@SerializedName("amount")
public Long amount;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("goods_detail")
public List<GoodsDetail> goodsDetail;
}
public static class FundsFromItem {
@SerializedName("account")
public Account account;
@SerializedName("amount")
public Long amount;
}
public enum PromotionScope {
@SerializedName("GLOBAL")
GLOBAL,
@SerializedName("SINGLE")
SINGLE
}
public enum PromotionType {
@SerializedName("CASH")
CASH,
@SerializedName("NOCASH")
NOCASH
}
public static class GoodsDetail {
@SerializedName("merchant_goods_id")
public String merchantGoodsId;
@SerializedName("wechatpay_goods_id")
public String wechatpayGoodsId;
@SerializedName("goods_name")
public String goodsName;
@SerializedName("unit_price")
public Long unitPrice;
@SerializedName("refund_amount")
public Long refundAmount;
@SerializedName("refund_quantity")
public Long refundQuantity;
}
public enum Account {
@SerializedName("AVAILABLE")
AVAILABLE,
@SerializedName("UNAVAILABLE")
UNAVAILABLE
}
}

View File

@@ -0,0 +1,45 @@
# 支付成功回调通知
## 回调描述
用户使用普通支付APP支付/H5支付/JSAPI支付/Native支付/小程序支付功能当用户成功支付订单后微信支付会通过POST的请求方式向商户预先设置的回调地址APP支付/H5支付/JSAPI支付/Native支付/小程序支付下单接口传入的notify_url发送回调通知让商户知晓用户已完成支付。
> **注意**商户侧对微信支付回调IP有防火墙策略限制的需要对微信回调IP段开通白名单否则会导致收不到回调微信支付回调被商户防火墙拦截详情参考回调处理逻辑注意事项。
## 回调报文格式
微信支付会通过POST的方式向回调地址发送回调报文回调通知的请求主体中会包含JSON格式的通知参数
```json
{
"id": "EV-2018022511223320873",
"create_time": "2015-05-20T13:29:35+08:00",
"resource_type": "encrypt-resource",
"event_type": "TRANSACTION.SUCCESS",
"summary": "支付成功",
"resource": {
"original_type": "transaction",
"algorithm": "AEAD_AES_256_GCM",
"ciphertext": "",
"associated_data": "",
"nonce": ""
}
}
```
## 关键字段说明
| 字段 | 说明 |
|------|------|
| `event_type` | 事件类型,支付成功为 `TRANSACTION.SUCCESS` |
| `resource.algorithm` | 加密算法,固定为 `AEAD_AES_256_GCM` |
| `resource.ciphertext` | 密文需使用商户APIv3密钥解密后得到支付订单详情 |
| `resource.associated_data` | 附加数据,解密时作为 AAD 参数 |
| `resource.nonce` | 随机串,解密时作为 Nonce 参数 |
## 处理要求
1. 接收到回调后,先验证请求签名(使用微信支付公钥验签)
2. 使用商户APIv3密钥 + AEAD_AES_256_GCM 解密 `resource.ciphertext` 得到订单明文
3. 处理完成后返回 HTTP 200 + `{"code": "SUCCESS", "message": "成功"}` 表示确认收到
4. 若返回非200或超时微信支付会按策略重试通知

View File

@@ -0,0 +1,104 @@
# 基础支付 & 合单支付示例代码接口索引
> 根据用户确认的开发语言加载对应语言目录下的文件。Java 和 Go 目录结构一致。
## 下单(需确认支付方式)
| 支付方式 | 接口 | Java | Go |
|---------|------|------|-----|
| JSAPI/小程序 | POST /v3/pay/transactions/jsapi | `Java/1-JSAPI支付/JsapiPrepay.java` | `Go/1-JSAPI支付/JsapiPrepay.go` |
| APP | POST /v3/pay/transactions/app | `Java/2-APP支付/AppPrepay.java` | `Go/2-APP支付/AppPrepay.go` |
| H5 | POST /v3/pay/transactions/h5 | `Java/3-H5支付/H5Prepay.java` | `Go/3-H5支付/H5Prepay.go` |
| Native | POST /v3/pay/transactions/native | `Java/4-Native支付/NativePrepay.java` | `Go/4-Native支付/NativePrepay.go` |
## 调起支付(需确认支付方式,前端/客户端集成参考)
| 支付方式 | 调起方式 | Java |
|---------|---------|------|
| JSAPI | WeixinJSBridge | `Java/1-JSAPI支付/JsapiInvoke.md` |
| 小程序 | wx.requestPayment | `Java/5-小程序支付/MiniProgramInvoke.md` |
| APP | OpenSDK | `Java/2-APP支付/AppInvoke.md` |
| H5 | 跳转h5_url | `Java/3-H5支付/H5Invoke.md` |
| Native | code_url转二维码 | `Java/4-Native支付/NativeInvoke.md` |
## 通用接口(无需确认支付方式,各支付方式完全相同)
| 业务 | 接口 | Java | Go |
|------|------|------|-----|
| 微信订单号查单 | GET /v3/pay/transactions/id/{transaction_id} | `Java/6-订单查询/QueryByWxTradeNo.java` | `Go/6-订单查询/QueryByWxTradeNo.go` |
| 商户订单号查单 | GET /v3/pay/transactions/out-trade-no/{out_trade_no} | `Java/6-订单查询/QueryByOutTradeNo.java` | `Go/6-订单查询/QueryByOutTradeNo.go` |
| 关闭订单 | POST /v3/pay/transactions/out-trade-no/{out_trade_no}/close | `Java/7-关闭订单/CloseOrder.java` | `Go/7-关闭订单/CloseOrder.go` |
| 退款申请 | POST /v3/refund/domestic/refunds | `Java/8-订单退款/CreateRefund.java` | `Go/8-订单退款/CreateRefund.go` |
| 查询退款 | GET /v3/refund/domestic/refunds/{out_refund_no} | `Java/8-订单退款/QueryByOutRefundNo.java` | `Go/8-订单退款/QueryByOutRefundNo.go` |
| 异常退款 | POST /v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund | `Java/8-订单退款/CreateAbnormalRefund.java` | `Go/8-订单退款/CreateAbnormalRefund.go` |
| 支付回调通知 | 回调报文格式与处理要求 | `Java/9-支付回调通知/支付成功回调通知说明.md` | — |
| 退款回调通知 | 回调报文格式与处理要求 | `Java/10-退款结果回调通知/退款结果回调通知说明.md` | — |
| 申请交易账单 | GET /v3/bill/tradebill | `Java/11-申请交易账单/GetTradeBill.java` | `Go/11-申请交易账单/GetTradeBill.go` |
| 申请资金账单 | GET /v3/bill/fundflowbill | `Java/12-申请资金账单/GetFundFlowBill.java` | `Go/12-申请资金账单/GetFundFlowBill.go` |
| 下载账单 | GET download_url | `Java/13-下载账单/DownloadBill.java` | — |
---
## 合单支付下单(需确认支付方式)
> 合单支付不是独立的支付方式,而是将多个子订单合并为一笔支付的模式。下单 API 路径为 `/v3/combine-transactions/{type}`,与基础支付不同。
| 支付方式 | 接口 | Java | Go |
|---------|------|------|-----|
| JSAPI/小程序 | POST /v3/combine-transactions/jsapi | `Java/15-合单支付/UnionJsapiPrepay.java` | `Go/15-合单支付/UnionJsapiPrepay.go` |
| APP | POST /v3/combine-transactions/app | `Java/15-合单支付/UnionAppPrepay.java` | `Go/15-合单支付/UnionAppPrepay.go` |
| H5 | POST /v3/combine-transactions/h5 | `Java/15-合单支付/UnionH5Prepay.java` | `Go/15-合单支付/UnionH5Prepay.go` |
| Native | POST /v3/combine-transactions/native | `Java/15-合单支付/UnionNativePrepay.java` | `Go/15-合单支付/UnionNativePrepay.go` |
> 合单支付的**调起支付**与基础支付完全一致(返回的 `prepay_id` / `h5_url` / `code_url` 用法相同),直接复用上方「调起支付」中的文件即可。
## 合单支付专用接口(与基础支付不同,必须使用合单专用接口)
> 合单订单的查单和关单**不能**使用基础支付的查单/关单接口,必须使用以下合单专用接口。
| 业务 | 接口 | Java | Go |
|------|------|------|-----|
| 查询合单订单 | GET /v3/combine-transactions/out-trade-no/{combine_out_trade_no} | `Java/15-合单支付/UnionQueryByOutTradeNo.java` | `Go/15-合单支付/UnionQueryByOutTradeNo.go` |
| 关闭合单订单 | POST /v3/combine-transactions/out-trade-no/{combine_out_trade_no}/close | `Java/15-合单支付/UnionClose.java` | `Go/15-合单支付/UnionClose.go` |
| 合单支付回调通知 | 回调报文格式与处理要求 | `Java/15-合单支付/合单支付成功回调通知说明.md` | — |
## 合单支付复用基础支付的接口
> 以下接口合单支付与基础支付完全相同,按**子单维度**操作。退款时使用子单的 `transaction_id`,账单以子单维度记录在各子单商户账单内。
| 业务 | 接口 | 参考文件 |
|------|------|---------|
| 退款申请 | POST /v3/refund/domestic/refunds | `Java/8-订单退款/CreateRefund.java``Go/8-订单退款/CreateRefund.go` |
| 查询退款 | GET /v3/refund/domestic/refunds/{out_refund_no} | `Java/8-订单退款/QueryByOutRefundNo.java``Go/8-订单退款/QueryByOutRefundNo.go` |
| 异常退款 | POST /v3/refund/domestic/refunds/{refund_id}/apply-abnormal-refund | `Java/8-订单退款/CreateAbnormalRefund.java``Go/8-订单退款/CreateAbnormalRefund.go` |
| 退款回调通知 | 回调报文格式与处理要求 | `Java/10-退款结果回调通知/退款结果回调通知说明.md` |
| 申请交易账单 | GET /v3/bill/tradebill | `Java/11-申请交易账单/GetTradeBill.java``Go/11-申请交易账单/GetTradeBill.go` |
| 申请资金账单 | GET /v3/bill/fundflowbill | `Java/12-申请资金账单/GetFundFlowBill.java``Go/12-申请资金账单/GetFundFlowBill.go` |
| 下载账单 | GET download_url | `Java/13-下载账单/DownloadBill.java` |
## 16. 分账
| 接口 | Java | Go |
|------|------|------|
| 请求分账 | `Java/16-分账/CreateOrder.java` | `Go/16-分账/CreateOrder.go` |
| 查询分账结果 | `Java/16-分账/QueryOrder.java` | `Go/16-分账/QueryOrder.go` |
| 请求分账回退 | `Java/16-分账/CreateReturnOrder.java` | `Go/16-分账/CreateReturnOrder.go` |
| 查询分账回退结果 | `Java/16-分账/QueryReturnOrder.java` | `Go/16-分账/QueryReturnOrder.go` |
| 解冻剩余资金 | `Java/16-分账/UnfreezeOrder.java` | `Go/16-分账/UnfreezeOrder.go` |
| 查询剩余待分金额 | `Java/16-分账/QueryOrderAmount.java` | `Go/16-分账/QueryOrderAmount.go` |
| 添加分账接收方 | `Java/16-分账/AddReceiver.java` | `Go/16-分账/AddReceiver.go` |
| 删除分账接收方 | `Java/16-分账/DeleteReceiver.java` | `Go/16-分账/DeleteReceiver.go` |
| 申请分账账单 | `Java/16-分账/SplitBill.java` | `Go/16-分账/SplitBill.go` |
> 分账规则、API 列表见 `3-商户与服务商通用/接入指南/分账接入指南.md`。
## SDK工具类所有接口的公共依赖
> 所有示例代码都依赖此工具类提供签名、验签、加解密、HTTP请求等基础能力。提醒用户需一并集成。
| 语言 | 文件 |
|------|------|
| Java | `Java/14-SDK工具类/WXPayUtility.java` — 签名、验签、加解密等基础能力 |
| Java | `Java/14-SDK工具类/WXPayClient.java` — HTTP 客户端,封装请求签名→发送→验签流程 |
| Go | `Go/14-SDK工具类/wxpay_utility.go` — 签名、验签、加解密等基础能力 |
| Go | `Go/14-SDK工具类/wxpay_client.go` — HTTP 客户端,封装请求签名→发送→验签流程 |