master #22
@@ -0,0 +1,4 @@
|
||||
Set-Location 'C:\Genarrative'
|
||||
$env:RUST_SERVER_TARGET = 'http://127.0.0.1:8082'
|
||||
$env:GENARRATIVE_RUNTIME_SERVER_TARGET = 'http://127.0.0.1:8082'
|
||||
npm.cmd run dev:web *> 'C:\Genarrative\.codex\logs\dev-web-final.out.log'
|
||||
@@ -47,7 +47,7 @@ Default body:
|
||||
}
|
||||
```
|
||||
|
||||
For a reference image, add:
|
||||
For weak visual references in text-to-image generation, add:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -55,6 +55,26 @@ For a reference image, add:
|
||||
}
|
||||
```
|
||||
|
||||
For image-to-image work that must follow a reference image closely, use the VectorEngine edits endpoint instead of the generations `image` array:
|
||||
|
||||
```text
|
||||
POST {VECTOR_ENGINE_BASE_URL}/v1/images/edits
|
||||
Authorization: Bearer {VECTOR_ENGINE_API_KEY}
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
Multipart fields:
|
||||
|
||||
```text
|
||||
model=gpt-image-2
|
||||
prompt=<prompt>
|
||||
n=1
|
||||
size=1024x1024
|
||||
image=@reference.png
|
||||
```
|
||||
|
||||
Prefer edits for workflows where the reference image controls composition, pose, container shape, or layout. In this repository, Match3D container UI generation uses edits with `public/match3d-background-references/pot-fused-reference.png` as the `image` part.
|
||||
|
||||
Accept image output from `data[].url`, `data[].b64_json`, or direct nested `url` fields. VectorEngine GPT-image-2-all currently returns synchronously; do not poll APIMart task endpoints.
|
||||
|
||||
## Environment
|
||||
|
||||
@@ -0,0 +1,351 @@
|
||||
import { Buffer } from 'node:buffer';
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const skillRoot = path.resolve(__dirname, '..');
|
||||
const repoRoot = path.resolve(skillRoot, '..', '..', '..');
|
||||
const defaultOutDir = path.join(repoRoot, 'public', 'anthro-cat-illustrations');
|
||||
const defaultTimeoutMs = 1000000;
|
||||
|
||||
const prompts = [
|
||||
{
|
||||
id: 'cat-barista',
|
||||
title: '咖啡师猫咪',
|
||||
subject:
|
||||
'一只奶油色猫咪像人一样双足站立,穿深绿色围裙,在温暖咖啡馆吧台前专注拉花,爪子扶着咖啡杯,蓬松尾巴自然弯起,童书级精致插画,柔和自然光,主体清晰。',
|
||||
},
|
||||
{
|
||||
id: 'cat-detective',
|
||||
title: '侦探猫咪',
|
||||
subject:
|
||||
'一只黑白猫咪像侦探一样双足站在雨后街角,穿短风衣和小帽子,单爪拿放大镜,另一只爪插兜,路灯和湿润石板路反光,电影感但可爱,插画风格。',
|
||||
},
|
||||
{
|
||||
id: 'cat-dancer',
|
||||
title: '舞者猫咪',
|
||||
subject:
|
||||
'一只橘猫以拟人舞者姿态单脚旋转,穿轻盈舞台披肩,前爪展开,尾巴形成优雅弧线,背景是暖色小剧场灯光,动作灵动,精致插画。',
|
||||
},
|
||||
{
|
||||
id: 'cat-knight',
|
||||
title: '骑士猫咪',
|
||||
subject:
|
||||
'一只银灰猫咪像小骑士一样站在苔藓石台上,披短斗篷,双爪握着细剑指向地面,姿态勇敢但可亲,远处森林微光,奇幻插画风格。',
|
||||
},
|
||||
{
|
||||
id: 'cat-painter',
|
||||
title: '画家猫咪',
|
||||
subject:
|
||||
'一只三花猫咪双足站在画架前,穿宽松蓝色工作衫,一爪拿画笔一爪托调色盘,鼻尖有颜料点,窗边画室阳光明亮,温柔手绘插画。',
|
||||
},
|
||||
{
|
||||
id: 'cat-astronaut',
|
||||
title: '宇航员猫咪',
|
||||
subject:
|
||||
'一只白猫咪以拟人宇航员姿态站在月面,透明头盔内露出猫脸,尾巴在宇航服后轻轻翘起,爪子向远处蓝色星球敬礼,梦幻插画风格。',
|
||||
},
|
||||
];
|
||||
|
||||
const args = new Map();
|
||||
for (let index = 2; index < process.argv.length; index += 1) {
|
||||
const raw = process.argv[index];
|
||||
if (raw.startsWith('--')) {
|
||||
const next = process.argv[index + 1];
|
||||
if (next && !next.startsWith('--')) {
|
||||
args.set(raw, next);
|
||||
index += 1;
|
||||
} else {
|
||||
args.set(raw, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readDotenv(fileName) {
|
||||
const filePath = path.join(repoRoot, fileName);
|
||||
if (!existsSync(filePath)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const values = {};
|
||||
for (const line of readFileSync(filePath, 'utf8').split(/\r?\n/u)) {
|
||||
const trimmed = line.trim();
|
||||
if (!trimmed || trimmed.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u.exec(trimmed);
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
let value = match[2].trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
values[match[1]] = value;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function resolveEnv() {
|
||||
const loaded = {
|
||||
...readDotenv('.env.example'),
|
||||
...readDotenv('.env.local'),
|
||||
...readDotenv('.env.secrets.local'),
|
||||
...process.env,
|
||||
};
|
||||
return {
|
||||
baseUrl: String(loaded.VECTOR_ENGINE_BASE_URL || '')
|
||||
.trim()
|
||||
.replace(/\/+$/u, ''),
|
||||
apiKey: String(loaded.VECTOR_ENGINE_API_KEY || '').trim(),
|
||||
timeoutMs: Number.parseInt(
|
||||
String(loaded.VECTOR_ENGINE_IMAGE_REQUEST_TIMEOUT_MS || defaultTimeoutMs),
|
||||
10,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function buildVectorEngineImagesGenerationUrl(baseUrl) {
|
||||
return baseUrl.endsWith('/v1')
|
||||
? `${baseUrl}/images/generations`
|
||||
: `${baseUrl}/v1/images/generations`;
|
||||
}
|
||||
|
||||
function buildPrompt(entry) {
|
||||
return [
|
||||
'请生成一张高清 1:1 方形插画。',
|
||||
`画面主体:${entry.subject}`,
|
||||
'要求:猫咪保留清晰猫脸、猫耳、猫尾和毛发质感,但身体姿态像人一样自然;构图完整,角色占画面主体,适合作为项目插画素材。',
|
||||
'避免:文字、水印、边框、按钮、UI 元素、低清晰度、过度写实恐怖感、畸形肢体、多余手指。',
|
||||
].join('');
|
||||
}
|
||||
|
||||
function collectStringsByKey(value, targetKey, output) {
|
||||
if (Array.isArray(value)) {
|
||||
value.forEach((entry) => collectStringsByKey(entry, targetKey, output));
|
||||
return;
|
||||
}
|
||||
if (!value || typeof value !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, nested] of Object.entries(value)) {
|
||||
if (key === targetKey) {
|
||||
if (typeof nested === 'string' && nested.trim()) {
|
||||
output.push(nested.trim());
|
||||
}
|
||||
if (Array.isArray(nested)) {
|
||||
nested.forEach((entry) => {
|
||||
if (typeof entry === 'string' && entry.trim()) {
|
||||
output.push(entry.trim());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
collectStringsByKey(nested, targetKey, output);
|
||||
}
|
||||
}
|
||||
|
||||
function extractImageUrls(payload) {
|
||||
const urls = [];
|
||||
collectStringsByKey(payload, 'url', urls);
|
||||
collectStringsByKey(payload, 'image', urls);
|
||||
collectStringsByKey(payload, 'image_url', urls);
|
||||
return [...new Set(urls)].filter((url) => /^https?:\/\//u.test(url));
|
||||
}
|
||||
|
||||
function extractBase64Images(payload) {
|
||||
const values = [];
|
||||
collectStringsByKey(payload, 'b64_json', values);
|
||||
return values;
|
||||
}
|
||||
|
||||
function inferExtensionFromContentType(contentType) {
|
||||
const normalized = contentType.split(';')[0]?.trim().toLowerCase();
|
||||
if (normalized === 'image/png') {
|
||||
return 'png';
|
||||
}
|
||||
if (normalized === 'image/webp') {
|
||||
return 'webp';
|
||||
}
|
||||
if (normalized === 'image/gif') {
|
||||
return 'gif';
|
||||
}
|
||||
return 'jpg';
|
||||
}
|
||||
|
||||
function inferExtensionFromBytes(bytes) {
|
||||
if (bytes.subarray(0, 8).equals(Buffer.from('\x89PNG\r\n\x1A\n', 'binary'))) {
|
||||
return 'png';
|
||||
}
|
||||
if (bytes.subarray(0, 3).equals(Buffer.from([0xff, 0xd8, 0xff]))) {
|
||||
return 'jpg';
|
||||
}
|
||||
if (
|
||||
bytes.subarray(0, 4).toString('ascii') === 'RIFF' &&
|
||||
bytes.subarray(8, 12).toString('ascii') === 'WEBP'
|
||||
) {
|
||||
return 'webp';
|
||||
}
|
||||
return 'png';
|
||||
}
|
||||
|
||||
async function fetchJson(url, options, timeoutMs) {
|
||||
const abortController = new AbortController();
|
||||
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
...options,
|
||||
signal: abortController.signal,
|
||||
});
|
||||
const text = await response.text();
|
||||
if (!response.ok) {
|
||||
throw new Error(`VectorEngine ${response.status}: ${text.slice(0, 600)}`);
|
||||
}
|
||||
return JSON.parse(text);
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
throw new Error(`VectorEngine request timed out after ${timeoutMs}ms`);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadUrl(url, timeoutMs) {
|
||||
const abortController = new AbortController();
|
||||
const timer = setTimeout(() => abortController.abort(), timeoutMs);
|
||||
try {
|
||||
const response = await fetch(url, { signal: abortController.signal });
|
||||
if (!response.ok) {
|
||||
throw new Error(`download ${response.status}`);
|
||||
}
|
||||
const bytes = Buffer.from(await response.arrayBuffer());
|
||||
return {
|
||||
bytes,
|
||||
extension: inferExtensionFromContentType(
|
||||
response.headers.get('content-type') || 'image/jpeg',
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
if (error?.name === 'AbortError') {
|
||||
throw new Error(`Generated image download timed out after ${timeoutMs}ms`);
|
||||
}
|
||||
throw error;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
}
|
||||
}
|
||||
|
||||
async function generateOne(env, entry, outDir) {
|
||||
const requestBody = {
|
||||
model: 'gpt-image-2-all',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
};
|
||||
const payload = await fetchJson(
|
||||
buildVectorEngineImagesGenerationUrl(env.baseUrl),
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${env.apiKey}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(requestBody),
|
||||
},
|
||||
env.timeoutMs,
|
||||
);
|
||||
|
||||
const urls = extractImageUrls(payload);
|
||||
const b64Images = extractBase64Images(payload);
|
||||
|
||||
let image;
|
||||
if (urls[0]) {
|
||||
image = await downloadUrl(urls[0], env.timeoutMs);
|
||||
} else if (b64Images[0]) {
|
||||
const bytes = Buffer.from(b64Images[0], 'base64');
|
||||
image = {
|
||||
bytes,
|
||||
extension: inferExtensionFromBytes(bytes),
|
||||
};
|
||||
} else {
|
||||
throw new Error(`VectorEngine returned no image for ${entry.id}`);
|
||||
}
|
||||
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
const outputPath = path.join(outDir, `${entry.id}.${image.extension}`);
|
||||
writeFileSync(outputPath, image.bytes);
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
const dryRun = args.has('--dry-run') || !args.has('--live');
|
||||
const outDir = path.resolve(String(args.get('--out-dir') || defaultOutDir));
|
||||
const limit = Number.parseInt(String(args.get('--limit') || '0'), 10);
|
||||
const selectedPrompts = limit > 0 ? prompts.slice(0, limit) : prompts;
|
||||
|
||||
if (dryRun) {
|
||||
const env = resolveEnv();
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
mode: 'dry-run',
|
||||
outDir,
|
||||
count: selectedPrompts.length,
|
||||
hasBaseUrl: Boolean(env.baseUrl),
|
||||
hasApiKey: Boolean(env.apiKey),
|
||||
requests: selectedPrompts.map((entry) => ({
|
||||
id: entry.id,
|
||||
title: entry.title,
|
||||
body: {
|
||||
model: 'gpt-image-2-all',
|
||||
prompt: buildPrompt(entry),
|
||||
n: 1,
|
||||
size: '1024x1024',
|
||||
},
|
||||
})),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const env = resolveEnv();
|
||||
if (!env.baseUrl || !env.apiKey) {
|
||||
console.error(
|
||||
JSON.stringify({
|
||||
ok: false,
|
||||
error: 'Missing VECTOR_ENGINE_BASE_URL or VECTOR_ENGINE_API_KEY',
|
||||
hasBaseUrl: Boolean(env.baseUrl),
|
||||
hasApiKey: Boolean(env.apiKey),
|
||||
}),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const generated = [];
|
||||
for (const entry of selectedPrompts) {
|
||||
console.log(`Generating ${entry.id}...`);
|
||||
generated.push(await generateOne(env, entry, outDir));
|
||||
}
|
||||
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
ok: true,
|
||||
count: generated.length,
|
||||
files: generated,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
@@ -13,7 +13,7 @@ const promptsPath = path.join(
|
||||
'puzzle-template-prompts.json',
|
||||
);
|
||||
const defaultOutDir = path.join(repoRoot, 'public', 'puzzle-creation-templates');
|
||||
const defaultTimeoutMs = 180000;
|
||||
const defaultTimeoutMs = 1000000;
|
||||
|
||||
const args = new Map();
|
||||
for (let index = 2; index < process.argv.length; index += 1) {
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
name: wechatpay-basic-payment
|
||||
description: 微信支付基础支付解决方案,涵盖支付、退款账单、分账、商户进件、开户意愿确认,提供选型/代码示例/业务速查/质量评估/排障五大能力。Use when user mentions "JSAPI支付", "APP支付", "H5支付", "Native支付", "小程序支付", "付款码支付", "合单支付", "特约商户进件", "开户意愿确认", or asks to "推荐支付方式", "要支付接口代码示例", "排查支付或退款问题".
|
||||
author: wechatpay
|
||||
version: "1.0"
|
||||
---
|
||||
|
||||
# 微信支付基础支付 & 合单支付接入指引
|
||||
|
||||
## 全局交互规范
|
||||
|
||||
> ‼️ 以下规则适用于本技能所有能力、所有对话轮次,优先级高于各能力的局部规则。
|
||||
|
||||
1. **所有问题必须得到用户明确回答后才能继续。** 如果一次提出了多个问题,必须逐一检查每个问题是否都已获得用户的明确答复。对于未回答的问题,必须再次追问,**严禁对未回答的问题自行假设、推断或使用默认值**。
|
||||
2. **接入模式前置确认**:任何能力使用前须先确认**商户模式**或**服务商模式**,已明确则无需重复。两种模式的核心差异见 → [📄 接入模式说明.md](./references/3-商户与服务商通用/接入指南/接入模式说明.md)。
|
||||
3. **分步确认协议**(简单知识问答除外,需要帮用户排查、分析或执行操作时必须遵守):
|
||||
- **① 明确需求**:先理解用户问题,给出初步判断或原因分析,不要一上来就堆参数清单。
|
||||
- **② 征得同意**:主动提出下一步能做什么,**等用户明确同意后**才继续,严禁用户没表态就开始收集参数或执行操作。
|
||||
- **③ 收集信息**:用户同意后再告知需要哪些信息并逐项收集,收齐才能执行。
|
||||
- **④ 执行前确认**:准备执行操作前,简要说明即将做什么,确认用户同意后再执行;涉及线上环境须额外提示风险。
|
||||
|
||||
## 能力概览
|
||||
|
||||
1. **产品选型** — 根据场景推荐支付方式(JSAPI/APP/H5/Native/小程序/付款码),判断是否需要合单支付
|
||||
2. **示例代码** — 各接口的下单、调起、回调、退款、账单等代码结构示例(只展示不写入)
|
||||
3. **业务知识速查** — 订单状态、退款规则、账单对账、APPID绑定、特约商户进件、开户意愿确认等
|
||||
4. **接入质量评估** — 签名验签、业务逻辑完整性、回调处理规范性检查(含合单/分账/进件/开户意愿确认专项)
|
||||
5. **问题排查** — 下单失败、调起异常、回调收不到、退款失败等(含合单支付专项常见问题)
|
||||
|
||||
> 未明确支付方式时先通过能力1引导选型。退款和账单无需确认支付方式,但仍需确认接入模式。合单支付需先确认是否涉及多商户/多APPID场景。特约商户进件和商户开户意愿确认仅适用于服务商/渠道商模式。
|
||||
|
||||
## 能力1:产品选型
|
||||
|
||||
> 用户问「该用哪种支付方式」或比较各方式区别时 → 加载 `支付产品对比.md`,确定支付方式后再按需加载示例代码。
|
||||
|
||||
- 产品对比 + 选型决策树 + 准入条件 + 调起支付差异 → [📄 支付产品对比.md](./references/3-商户与服务商通用/产品选型/支付产品对比.md)
|
||||
|
||||
## 能力2:示例代码
|
||||
|
||||
> 用户要某个接口的代码示例时 → 确认接入模式和语言,加载对应模式的 `接口索引.md` 定位代码文件。
|
||||
>
|
||||
> ‼️ **只检索、不生成。** 严禁从零编写任何代码,必须从代码示例文件中检索获取。
|
||||
>
|
||||
> ‼️ **只展示、不写入。** 代码示例仅用于讲解 API 调用结构和签名流程,严禁直接写入用户项目(禁止调用 write_to_file、replace_in_file 等工具创建或修改项目文件)。在对话中展示代码,让用户自行复制适配。
|
||||
>
|
||||
> ‼️ **先交互、后输出。** 提供代码前必须先确认接入模式、开发语言和具体接口,每次只输出一个接口;提供完代码后主动推荐接入质量评估。
|
||||
>
|
||||
> ‼️ **支付方式仅「下单」和「调起支付」接口需确认,其他接口无需询问支付方式。** 用户请求查单、关单、退款、回调处理、账单等通用接口时,只需确认接入模式和开发语言,无需询问支付方式——这些接口各支付方式完全相同。**但合单支付的查单、关单、回调使用专用接口,需确认用户是基础支付还是合单支付。**
|
||||
>
|
||||
> ‼️ **用户语言非 Java/Go 时**(本 skill 仅维护 Java/Go 示例):**禁止**直接生成跨语言代码。流程:
|
||||
> 1. 用 `AskQuestion` 获明确同意(文案需明示「参考实现 / 非官方维护 / 须自行 review 与测试」),未同意只发官方 Java/Go 原文。
|
||||
> 2. 同意后以官方 Java 示例为基准翻译生成业务代码「参考实现」;再用纯文字问是否翻 Java 公库(SDK 工具类 + HTTP 客户端),未明确要不贴。每段代码前附下方免责块。
|
||||
>
|
||||
> > ⚠️ 以下代码为**跨语言参考实现**,由 AI 参考官方 Java 示例翻译生成,并非微信支付官方维护。
|
||||
> > - 请**逐行 review** 签名构造、HTTP 调用、字段命名、回调解密等关键逻辑。
|
||||
> > - 上线前必须在测试环境完整验证,建议先以官方 Java/Go 示例打通主链路作为对照。
|
||||
> > - 出现接入问题时以官方 Java/Go 示例为准。
|
||||
|
||||
- 涉及提供示例代码时,按接入模式查阅对应接口索引,定位目标代码文件:
|
||||
- 商户模式 → [📄 接口索引.md](./references/1-商户/示例代码/接口索引.md)
|
||||
- 服务商模式 → [📄 接口索引.md](./references/2-服务商/示例代码/接口索引.md)
|
||||
|
||||
> **加载策略**:先确认接入模式,读对应的 `接口索引.md` 定位用户需要的接口对应的文件路径,再按需加载具体文件。不要一次性加载所有文件。
|
||||
|
||||
## 能力3:业务知识速查
|
||||
|
||||
> 用户问参数获取、APPID绑定、订单状态、退款规则、分账等业务知识时 → 按接入模式加载对应文档。
|
||||
|
||||
- 开发必要参数 / APPID类型 / APPID绑定流程:
|
||||
- 商户模式 → [📄 开发必要参数说明.md](./references/1-商户/接入指南/开发必要参数说明.md)
|
||||
- 服务商模式 → [📄 开发必要参数说明.md](./references/2-服务商/接入指南/开发必要参数说明.md)
|
||||
- 点金计划(服务商 JSAPI 必接) → [📄 点金计划.md](./references/2-服务商/接入指南/点金计划.md)
|
||||
- 订单状态 / 关单 / 终态 → [📄 订单状态流转.md](./references/3-商户与服务商通用/接入指南/订单状态流转.md)
|
||||
- 分账 → [📄 分账接入指南.md](./references/3-商户与服务商通用/接入指南/分账接入指南.md)
|
||||
- 特约商户进件(仅服务商) → [📄 特约商户进件.md](./references/2-服务商/接入指南/特约商户进件.md)
|
||||
- 商户开户意愿确认(仅服务商/渠道商) → [📄 商户开户意愿确认.md](./references/2-服务商/接入指南/商户开户意愿确认.md)
|
||||
- 退款规则 / 账单对账 → 已整合到示例代码注释中,通过能力2加载
|
||||
|
||||
> **加载策略**:按关键词匹配文档,区分接入模式。特约商户进件和商户开户意愿确认为服务商/渠道商专属,商户模式无需加载。
|
||||
|
||||
## 能力4:接入质量评估
|
||||
|
||||
> 用户准备上线或想检查代码隐患时 → 加载以下文档。
|
||||
>
|
||||
> ‼️ **只检查用户实际使用的功能模块。** 合单支付、分账、进件、开户意愿确认等模块须先确认用户是否涉及,**未使用的不检查、不提及**。
|
||||
|
||||
- 签名验签 → [📄 签名与验签规则.md](./references/3-商户与服务商通用/接入指南/签名与验签规则.md)
|
||||
- 业务逻辑完整性(含质检人设 + 检查清单) → [📄 接入质量检查清单.md](./references/3-商户与服务商通用/接入指南/接入质量检查清单.md)
|
||||
- 回调处理规范 → [📄 回调通知处理.md](./references/3-商户与服务商通用/接入指南/回调通知处理.md)
|
||||
|
||||
## 能力5:问题排查
|
||||
|
||||
> 用户遇到报错或接口调用异常时 → 按下方路径分流加载。
|
||||
>
|
||||
> ‼️ **排障推荐示例代码时,必须先确认开发语言,只推荐对应的示例。** 排障手册中每个错误码的「示例代码推荐」可能涉及 Java/Go 两种语言示例,但输出时**只输出匹配的示例**。开发语言尚未确认时,先在推荐示例代码时自然地询问用户。
|
||||
>
|
||||
> ‼️ **用户语言非 Java/Go 时按能力 2 的跨语言确认流程处理**(弹框确认 → 参考生成 + 免责块 + 公库分步)。先用文字说明 Java/Go 示例中的关键修复点(签名、字段、流程),再走完整流程后再生成对应语言的"参考修复代码"。
|
||||
|
||||
- 排障手册(错误码 TOP 20 速查 + 定位流程 + 服务商特有问题)→ [📄 排障手册.md](./references/3-商户与服务商通用/问题排查/排障手册.md)
|
||||
- 基础支付常见问题 → [📄 基础支付常见问题.md](./references/3-商户与服务商通用/问题排查/基础支付常见问题.md)
|
||||
- 分账常见问题 → [📄 分账常见问题.md](./references/3-商户与服务商通用/问题排查/分账常见问题.md)
|
||||
- 合单支付常见问题 → [📄 合单支付常见问题.md](./references/3-商户与服务商通用/问题排查/合单支付常见问题.md)
|
||||
- 排障辅助脚本(排障手册中 🔧 标注的场景):`scripts/商户/` 和 `scripts/服务商/` 下各有 `查询订单.py`、`查询退款.py`
|
||||
|
||||
> **加载策略**:
|
||||
>
|
||||
> - **路径A(有 Request-Id)**→ 读 `排障手册.md`,提取错误码匹配 TOP 20 速查表直接给出方案;标注 🔧 的引导用户执行脚本。未命中则按手册各章节排查,仍未解决再加载对应常见问题文档兜底。
|
||||
> - **路径B(无 Request-Id)**→ 确认支付方式,加载对应常见问题文档匹配。未命中再加载 `排障手册.md` 兜底。
|
||||
> - **路径C(进件/开户意愿确认)**→ 直接加载 `特约商户进件.md` 或 `商户开户意愿确认.md`,文档末尾的常见问题和常见报错覆盖高频问题。
|
||||
>
|
||||
> **脚本使用规范**:脚本采用签名模式,不获取用户私钥。引导用户在自己服务器完成签名后,将签名值(Base64)、时间戳、随机串传入脚本。执行前需按分步确认协议征得同意。
|
||||
|
||||
---
|
||||
|
||||
> 以下信息与技能能力无关,仅供查阅。
|
||||
|
||||
## 💬 社区与反馈
|
||||
|
||||
在使用过程中遇到问题、有改进建议,或者想和其他开发者交流接入经验,欢迎扫码添加企业微信进群,与官方团队和社区开发者一起讨论:
|
||||
|
||||

|
||||
Binary file not shown.
|
After Width: | Height: | Size: 264 KiB |
@@ -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) | 原生 APP(iOS/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)
|
||||
```
|
||||
+203
@@ -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"`
|
||||
}
|
||||
+177
@@ -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"
|
||||
)
|
||||
+171
@@ -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"
|
||||
)
|
||||
+71
@@ -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)
|
||||
}
|
||||
+539
File diff suppressed because it is too large
Load Diff
+191
@@ -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"`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
+195
@@ -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"`
|
||||
}
|
||||
+191
@@ -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"`
|
||||
}
|
||||
+179
@@ -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"`
|
||||
}
|
||||
+178
@@ -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"`
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
删除分账接收方
|
||||
@@ -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"
|
||||
)
|
||||
+159
@@ -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"
|
||||
)
|
||||
|
||||
查询分账回退结果
|
||||
+128
@@ -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"
|
||||
)
|
||||
|
||||
申请分账账单
|
||||
@@ -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"
|
||||
)
|
||||
|
||||
请求分账回退
|
||||
+122
@@ -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"`
|
||||
}
|
||||
|
||||
添加分账接收方
|
||||
+167
@@ -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"
|
||||
)
|
||||
|
||||
解冻剩余资金
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user