基础支付
JSAPI支付
APP支付
Native支付
小程序支付
合单支付
付款码支付
经营能力
微信支付分
支付即服务
行业方案
智慧商圈
微信支付分停车服务
营销工具
代金券
商家券
委托营销
消费卡
支付有礼
小程序发券插件
H5发券
图片上传(营销专用)
现金红包
资金应用
付款
分账
风险合规
消费者投诉2.0
其他能力
清关报关
图片上传
视频上传

支付分开发指引(需确认模式)

1. 接口规则

为了在保证支付安全的前提下,带给商户简单、一致且易用的开发体验,我们推出了全新的微信支付APIv3接口。该版本API的具体规则请参考“APIv3接口规则

2. 开发准备

2.1. 搭建和配置开发环境

为了帮助开发者调用开放接口,我们提供了JAVA、PHP、GO三种语言版本的开发库,封装了签名生成、签名验证、敏感信息加/解密、媒体文件上传等基础功能(更多语言版本的开发库将在近期陆续提供

测试步骤

1、根据自身开发语言,选择对应的开发库并构建项目,具体配置请参考下面链接的详细说明:

    • wechatpay-apache-httpclient,适用于使用Apache HttpClient处理HTTP的Java开发者

    • wechatpay-php(推荐)、wechatpay-guzzle-middleware,适用于PHP开发者

    注:当前开发指引接口PHP示例代码采用wechatpay-guzzle-middleware版本

    • wechatpay-go,适用于Go开发者

更多资源可前往微信支付开发者社区搜索查看

2、创建加载商户私钥、加载平台证书、初始化httpClient的通用方法


@Before
public void setup() throws IOException {
    // 加载商户私钥(privateKey:私钥字符串)
    PrivateKey merchantPrivateKey = PemUtil
            .loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
 
    // 加载平台证书(mchId:商户号,mchSerialNo:商户证书序列号,apiV3Key:V3密钥)
    AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
            new WechatPay2Credentials(mchId, new PrivateKeySigner(mchSerialNo, merchantPrivateKey)),apiV3Key.getBytes("utf-8"));
 
    // 初始化httpClient
    httpClient = WechatPayHttpClientBuilder.create()
            .withMerchant(mchId, mchSerialNo, merchantPrivateKey)
            .withValidator(new WechatPay2Validator(verifier)).build();
}
 
@After
public void after() throws IOException {
    httpClient.close();
}

use GuzzleHttp\Exception\RequestException;
use WechatPay\GuzzleMiddleware\WechatPayMiddleware;
use WechatPay\GuzzleMiddleware\Util\PemUtil;
use GuzzleHttp\HandlerStack;
 
// 商户相关配置,
$merchantId = '1000100'; // 商户号
$merchantSerialNumber = 'XXXXXXXXXX'; // 商户API证书序列号
$merchantPrivateKey = PemUtil::loadPrivateKey('./path/to/mch/private/key.pem'); // 商户私钥文件路径
 
// 微信支付平台配置
$wechatpayCertificate = PemUtil::loadCertificate('./path/to/wechatpay/cert.pem'); // 微信支付平台证书文件路径
 
// 构造一个WechatPayMiddleware
$wechatpayMiddleware = WechatPayMiddleware::builder()
    ->withMerchant($merchantId, $merchantSerialNumber, $merchantPrivateKey) // 传入商户相关配置
    ->withWechatPay([ $wechatpayCertificate ]) // 可传入多个微信支付平台证书,参数类型为array
    ->build();
 
// 将WechatPayMiddleware添加到Guzzle的HandlerStack中
$stack = GuzzleHttp\HandlerStack::create();
$stack->push($wechatpayMiddleware, 'wechatpay');
 
// 创建Guzzle HTTP Client时,将HandlerStack传入,接下来,正常使用Guzzle发起API请求,WechatPayMiddleware会自动地处理签名和验签
$client = new GuzzleHttp\Client(['handler' => $stack]);

/*
    Package core 微信支付api v3 go http-client 基础库,你可以使用它来创建一个client,并向微信支付发送http请求
    只需要你在初始化客户端的时候,传递credential以及validator
    credential用来生成http header中的authorization信息
    validator则用来校验回包是否被篡改
    如果http请求返回的err为nil,一般response.Body 都不为空,你可以尝试对其进行序列化
    请注意及时关闭response.Body
    注意:使用微信支付apiv3 go库需要引入相关的包,该示例代码必须引入的包名有以下信息

    "context"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "log"
    "github.com/wechatpay-apiv3/wechatpay-go/core"
    "github.com/wechatpay-apiv3/wechatpay-go/core/option"
    "github.com/wechatpay-apiv3/wechatpay-go/utils"

    */
func SetUp() (opt []option.ClientOption, err error) {
    //商户号
    mchID := ""
    //商户证书序列号
    mchCertSerialNumber := ""
    //商户私钥文件路径
    privateKeyPath := ""
    //平台证书文件路径
    wechatCertificatePath := ""

    // 加载商户私钥
    privateKey, err := utils.LoadPrivateKeyWithPath(privateKeyPath)
    if err != nil {
        log.Printf("load private err:%s", err.Error())
        return nil, err
    }
    // 加载微信支付平台证书
    wechatPayCertificate, err := utils.LoadCertificateWithPath(wechatCertificatePath)
    if err != nil {
        log.Printf("load certificate err:%s",err)
        return nil, err
    }
    //设置header头中authorization信息
    opts := []option.ClientOption{
        option.WithMerchant(mchID, mchCertSerialNumber, privateKey), // 设置商户相关配置
        option.WithWechatPay([]*x509.Certificate{wechatPayCertificate}), // 设置微信支付平台证书,用于校验回包信息用
    }
    return opts, nil
}

3、基于接口的示例代码,替换请求参数后可发起测试

说明:

• 上面的开发库为微信支付官方开发库,其它没有审核或者控制下的第三方工具和库,微信支付不保证它们的安全性和可靠性

通过包管理工具引入SDK后,可根据下面每个接口的示例代码替换相关参数后进行快速测试

开发者如果想详细了解签名生成、签名验证、敏感信息加/解密、媒体文件上传等常用方法的具体代码实现,可阅读下面的详细说明:

    1.签名生成

    2.签名验证

    3.敏感信息加解密

    4.merchantPrivateKey(私钥)

    5.wechatpayCertificates(平台证书)

    6.APIV3Key(V3 key)

如想更详细的了解我们的接口规则,可查看我们的接口规则指引文档

2.2. 业务开发配置

1、微信支付分的相关配置参数在商户入驻的过程中都已经配置完成(前往查看配置相关内容),例如授权结果回调url、service_notify_url、测试白名单、免确认订单模式的权限等。

2、如果发现配置信息有误,请主动联系微信支付分运营同学协助修改,或者点击右侧导航栏进入在线技术客服进行技术咨询。


3. 快速接入(需确认模式)

3.1. 业务流程图

重点步骤说明:

步骤1 用户在商户侧下单,商户调用后台接口创建支付分订单,通过接口返回获得跳转微信支付分小程序进行订单确认的必填参数“package”

我们通过此接口来创建支付分订单:创建支付分订单API

  • 入参“need_user_confirm”,取值请选择 “true”;
  • 入参“risk_fund:name”,取值请选择【先免模式】中的枚举值。

步骤2 调用前端方法跳转至微信,让用户完成确认订单操作

我们通过以下前端方法可调起微信客户端(注意区分场景):


步骤3 用户确认订单完成,商户系统将收到确认订单回调通知,此时可为用户提供服务;

我们通过以下接口将用户确认订单信息回调通知给商户系统:

确认订单回调通知API

在用户确认订单过程中可能出现长时间未收到回调、用户确认失败等情况,商户可先通过后台接口查询订单状态:

查询支付分订单API

根据查询到的订单状态结果,商户可决定是否需要取消当前订单,后台接口为:

取消支付分订单API


步骤4 用户服务结束,商户通过后台接口完结支付分订单;

调用完结支付分订单API接口后,微信支付分就会发起对用户的扣款,但是在用户扣款过程中可能会出现一些特殊情况,下面列举了几种特殊情况以及对应的处理方法,供大家参考:

请求:

• 情况一:扣款过程中,发现扣款金额有误(注意:此时需要订单为“待支付”状态)

处理方法1:调用修改订单金额接口修改订单金额,系统将按照修改后的金额发起用户扣款

处理方法2:调用取消支付分订单接口取消待支付的支付分订单

• 情况二:扣款过程中,由于某些原因,商户想立刻发起一次用户扣款

处理方法:商户调用商户发起催收扣款接口对待支付的支付分订单进行主动催收

• 情况三:扣款过程中,用户通过其它方式完成了支付,希望微信支付分停止继续扣款

处理方法:商户调用同步服务订单信息接口将订单支付成功状态同步给微信支付分,微信支付分将停止继续扣款的操作


步骤5 收到用户扣款成功通知,业务流程结束

通过支付成功回调通知API接口可以获取到用户扣款成功的通知,同时,商户也可以根据情况通过查询支付分订单API接口主动查询扣款情况。

  • 如订单状态state=DONE,且收款状态collection.state=USER_PAID,代表扣款成功
  • 如订单状态state=DOING,state_description=MCH_COMPLETE,且收款状态collection.state=USER_PAYING,代表扣款进行中

如遇到网络、服务器等原因造成无法正常接收扣款成功通知,一般有两种解决方法:

1.主动查单,通过查询支付分订单API 接口主动查询扣款情况

2.次日对账,通过申请交易账单API接口申请交易账单,再通过后台接口下载账单API下载交易账单


3.2. API接入(含示例代码)

本文档展示了如何使用微信支付服务端 SDK 快速接入微信支付分产品,完成与微信支付对接的部分。

注意:

  • 文档中的代码示例是用来阐述 API 基本使用方法,代码中的示例参数需替换成商户自己账号及请求参数才能跑通。
  • 以下接入步骤仅提供参考,请商户结合自身业务需求进行评估、修改。
3.2.1.【服务端】创建支付分订单

步骤说明:完成用户授权后,即可创建支付分订单,为用户提供服务了。

示例代码

public void CreateServiceOrder() throws Exception{
  
    //请求URL
    HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder");
    // 请求body参数
    String reqdata = "{"
            + "\"out_order_no\":\"1234323JKHDFE1243252\","
            + "\"appid\":\"wxd678efh567hg6787\","
            + "\"service_id\":\"500001\","
            + "\"service_introduction\":\"某某酒店\","
            + "\"post_payments\": ["
            + "{"
            + "\"name\":\"就餐费用服务费\","
            + "\"amount\":4000,"
            + "\"description\":\"就餐人均100元服务费:100/小时\","
            + "\"count\":1"
            + "}"
            + "],"
            + "\"post_discounts\": ["
            + "{"
            + "\"name\":\"满20减1元\","
            + "\"description\":\"不与其他优惠叠加\""
            + "}"
            + "],"
            + "\"time_range\": {"
            + "\"start_time\":\"20091225091010\","
            + "\"end_time\":\"20091225121010\""
            + "},"
            + "\"location\": {"
            + "\"start_location\":\"嗨客时尚主题展餐厅\","
            + "\"end_location\":\"嗨客时尚主题展餐厅\""
            + "},"
            + "\"risk_fund\": {"
            + "\"name\":\"ESTIMATE_ORDER_COST\","
            + "\"amount\":10000,"
            + "\"description\":\"就餐的预估费用\""
            + "},"
            + "\"attach\":\"Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald\","
            + "\"notify_url\":\"https://api.test.com\","
            + "\"openid\":\"oUpF8uMuAJO_M2pxb1Q9zNjWeS6o\","
            + "\"need_user_confirm\":true"
            + "}";
    StringEntity entity = new StringEntity(reqdata,"utf-8");
    entity.setContentType("application/json");
    httpPost.setEntity(entity);
    httpPost.setHeader("Accept", "application/json");
    
    //完成签名并执行请求
    CloseableHttpResponse response = httpClient.execute(httpPost);
    
    try {
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode == 200) { //处理成功
            System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
        } else if (statusCode == 204) { //处理成功,无返回Body
            System.out.println("success");
        } else {
            System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
            throw new IOException("request failed");
        }
    } finally {
        response.close();
    }
  }
try {
    $resp = $client->request(
        'POST',
        'https://api.mch.weixin.qq.com/v3/payscore/serviceorder', //请求URL
        [
            // JSON请求体
            'json' => [
                "out_order_no" => "1234323JKHDFE1243252",
                "appid" => "wxd678efh567hg6787",
                "service_id" => "500001",
                "service_introduction" => "某某酒店",
                "post_payments" => [
                    [
                        "name" => "就餐费用服务费",
                        "amount" => 4000,
                        "description" => "就餐人均100元服务费:100/小时",
                        "count" => 1,
                    ],
                ],
                "post_discounts" => [
                    [
                        "name" => "满20减1元",
                        "description" => "不与其他优惠叠加",
                    ],
                ],
                "time_range" => [
                    "start_time" => "20091225091010",
                    "end_time" => "20091225121010",
                ],
                "location" => [
                    "start_location" => "嗨客时尚主题展餐厅",
                    "end_location" => "嗨客时尚主题展餐厅",
                ],
                "risk_fund" => [
                    "name" => "ESTIMATE_ORDER_COST",
                    "amount" => 10000,
                    "description" => "就餐的预估费用",
                ],
                "attach" => "Easdfowealsdkjfnlaksjdlfkwqoi&wl3l2sald",
                "notify_url" => "https://api.test.com",
                "openid" => "oUpF8uMuAJO_M2pxb1Q9zNjWeS6o",
                "need_user_confirm" => true,
            ],
            'headers' => [ 'Accept' => 'application/json' ]
        ]
    );
    $statusCode = $resp->getStatusCode();
    if ($statusCode == 200) { //处理成功
        echo "success,return body = " . $resp->getBody()->getContents()."\n";
    } else if ($statusCode == 204) { //处理成功,无返回Body
        echo "success";
    }
  } catch (RequestException $e) {
    // 进行错误处理
    echo $e->getMessage()."\n";
    if ($e->hasResponse()) {
        echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
    }
    return;
  }

重要参数说明

• 入参“need_user_confirm”,取值请选择 “true”;

• 入参“risk_fund:name”,取值请选择【先免模式】中的枚举值。

更多参数、响应详情及错误码请参见创建支付分订单API接口文档

3.2.2.【客户端】跳转确认订单页

微信支付根据用户不同的使用场景(APP、小程序、微信外H5)分别提供了对应跳转确认订单页的方法,请根据场景进行选择,详见跳转确认订单页API文档

3.2.3.【服务端】确认订单回调通知

步骤说明:当用户确认订单时,微信会把相关信息异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答

注意:
  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

更多参数、响应详情及错误码请参见确认订单回调通知API接口文档

3.2.4.【服务端】查询支付分订单

步骤说明:一般在创建订单后、订单完结成功后等关键流程中,商户可能有知晓订单状态的需求,此时即可通过该接口查询订单状态。

示例代码:
public void GetServiceOrder() throws Exception{

  //请求URL
  HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/payscore/serviceorder?service_id=500001&out_order_no=8416518464133&appid=wxd678efh567hg6787");
  HttpGet.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(HttpGet);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'GET',
      'https://api.mch.weixin.qq.com/v3/payscore/serviceorder?service_id=500001&out_order_no=8416518464133&appid=wxd678efh567hg6787', //请求URL
      [
          'headers' => [ 'Accept' => 'application/json']
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
} catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
}
3.2.5. 【服务端】取消支付分订单

步骤说明:订单为以下状态时可以取消订单:CREATED(已创单)、DOING(进行中)(包括商户完结支付分订单后,且支付分订单收款状态为待支付USER_PAYING)。

示例代码

public void CancelServiceOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/cancel");
  // 请求body参数
  String reqdata = "{"
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"service_id\":\"500001\","
          + "\"reason\":\"用户投诉\""
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'POST',
      'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/cancel', //请求URL
      [
          // JSON请求体
          'json' => [
              "appid" => "wxd678efh567hg6787",
              "service_id" => "500001",
              "reason" => "用户投诉",
          ],
          'headers' => [ 'Accept' => 'application/json' ]
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
  } catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
  }

重要参数说明:

  • 入参“need_user_confirm”,取值请选择 “true”;
  • 入参“risk_fund:name”,取值请选择【先免模式】中的枚举值。

更多参数、响应详情及错误码请参见 取消支付分订单API接口文档

3.2.6.【服务端】完结支付分订单

步骤说明:用户服务结束后,商户通过请求完结支付分订单接口,通过微信支付分进行用户扣款操作。

注意:


public void CompleteServiceOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/complete");
  // 请求body参数
  String reqdata = "{"
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"service_id\":\"500001\","
          + "\"post_payments\": ["
          + "{"
          + "\"name\":\"就餐费用服务费\","
          + "\"amount\":4000,"
          + "\"description\":\"就餐人均100元服务费:100/小时\","
          + "\"count\":1"
          + "}"
          + "],"
          + "\"post_discounts\": ["
          + "{"
          + "\"name\":\"满20减1元\","
          + "\"description\":\"不与其他优惠叠加\","
          + "\"amount\":4000"
          + "}"
          + "],"
          + "\"total_amount\":3900,"
          + "\"time_range\": {"
          + "\"start_time\":\"20091225091010\","
          + "\"end_time\":\"20091225121010\""
          + "},"
          + "\"location\": {"
          + "\"end_location\":\"嗨客时尚主题展餐厅\""
          + "},"
          + "\"profit_sharing\":false"
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'POST',
      'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/complete', //请求URL
      [
          // JSON请求体
          'json' => [
              "appid" => "wxd678efh567hg6787",
              "service_id" => "500001",
              "post_payments" => [
                  [
                      "name" => "就餐费用服务费",
                      "amount" => 4000,
                      "description" => "就餐人均100元服务费:100/小时",
                      "count" => 1,
                  ],
              ],
              "post_discounts" => [
                  [
                      "name" => "满20减1元",
                      "description" => "不与其他优惠叠加",
                      "amount" => 4000,
                  ],
              ],
              "total_amount" => 3900,
              "time_range" => [
                  "start_time" => "20091225091010",
                  "end_time" => "20091225121010",
              ],
              "location" => [
                  "end_location" => "嗨客时尚主题展餐厅",
              ],
              "profit_sharing" => false,
          ],
          'headers' => [ 'Accept' => 'application/json' ]
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
} catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
}

更多参数、响应详情及错误码请参见完结支付分订单API接口文档

3.2.7. 【服务端】修改订单金额

步骤说明:在用户扣款成功前、完结订单后(即订单状态为“待支付”),如需修改订单支付金额,可通过此接口进行订单金额修改。修改成功后,微信支付将按照修改后的金额进行用户扣款。

示例代码

public void ModifyServiceOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/modify");
  // 请求body参数
  String reqdata = "{"
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"service_id\":\"500001\","
          + "\"post_payments\": ["
          + "{"
          + "\"name\":\"就餐费用服务费\","
          + "\"amount\":4000,"
          + "\"description\":\"就餐人均100元服务费:100/小时\","
          + "\"count\":1"
          + "}"
          + "],"
          + "\"post_discounts\": ["
          + "{"
          + "\"name\":\"满20减1元\","
          + "\"description\":\"不与其他优惠叠加\","
          + "\"amount\":100"
          + "}"
          + "],"
          + "\"total_amount\":2000,"
          + "\"reason\":\"用户投诉\""
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");

  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);

  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
  }
try {
  $resp = $client->request(
      'POST',
      'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/modify', //请求URL
      [
          // JSON请求体
          'json' => [
              "appid" => "wxd678efh567hg6787",
              "service_id" => "500001",
              "post_payments" => [
                  [
                      "name" => "就餐费用服务费",
                      "amount" => 4000,
                      "description" => "就餐人均100元服务费:100/小时",
                      "count" => 1,
                  ],
              ],
              "post_discounts" => [
                  [
                      "name" => "满20减1元",
                      "description" => "不与其他优惠叠加",
                      "amount" => 100,
                  ],
              ],
              "total_amount" => 2000,
              "reason" => "用户投诉",
          ],
          'headers' => [ 'Accept' => 'application/json' ]
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
  } catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
  }

更多参数、响应详情及错误码请参见修改订单金额API接口文档

3.2.8 【服务端】商户发起催收扣款

步骤说明:在订单完结成功后,支付成功前,商户可随时发起催收扣款,发起成功后微信支付将立即进行一次用户扣款(不保证一定能扣成功,仅执行一次扣款操作)。

示例代码

public void CollectServiceOrder() throws Exception{

  //请求URL
  HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/pay");
  // 请求body参数
  String reqdata = "{"
          + "\"appid\":\"wxd678efh567hg6787\","
          + "\"service_id\":\"500001\""
          + "}";
  StringEntity entity = new StringEntity(reqdata,"utf-8");
  entity.setContentType("application/json");
  httpPost.setEntity(entity);
  httpPost.setHeader("Accept", "application/json");
  
  //完成签名并执行请求
  CloseableHttpResponse response = httpClient.execute(httpPost);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) { //处理成功
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) { //处理成功,无返回Body
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
  'POST',
  'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/pay', //请求URL
  [
      // JSON请求体
      'json' => [
          "appid" => "wxd678efh567hg6787",
          "service_id" => "500001",
      ],
      'headers' => [ 'Accept' => 'application/json' ]
  ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
  echo "success,return body = " . $resp->getBody()->getContents()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
  echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
  echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见商户发起催收扣款API接口文档

3.2.9【服务端】同步服务订单信息

步骤说明:由于一些原因,用户与商户达成线下支付或者其他支付方式支付的协议,商户可通过此接口告知微信支付该笔订单无需继续扣款,微信支付在接到此信息后将不再发起用户扣款。

示例代码:

public void SyncServiceOrder() throws Exception{
//请求URL
HttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/sync");
// 请求body参数
String reqdata = "{"
        + "\"appid\":\"wxd678efh567hg6787\","
        + "\"service_id\":\"500001\","
        + "\"type\":\"Order_Paid\","
        + "\"detail\": {"
        + "\"paid_time\":\"20091225091210\""
        + "}"
        + "}";
StringEntity entity = new StringEntity(reqdata,"utf-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
httpPost.setHeader("Accept", "application/json");

//完成签名并执行请求
CloseableHttpResponse response = httpClient.execute(httpPost);

try {
    int statusCode = response.getStatusLine().getStatusCode();
    if (statusCode == 200) { //处理成功
        System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
    } else if (statusCode == 204) { //处理成功,无返回Body
        System.out.println("success");
    } else {
        System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
        throw new IOException("request failed");
    }
} finally {
    response.close();
}
}
try {
  $resp = $client->request(
  'POST',
  'https://api.mch.weixin.qq.com/v3/payscore/serviceorder/{out_order_no}/sync', //请求URL
  [
      // JSON请求体
      'json' => [
          "appid" => "wxd678efh567hg6787",
          "service_id" => "500001",
          "type" => "Order_Paid",
          "detail" => [
              "paid_time" => "20091225091210",
          ]
      ],
      'headers' => [ 'Accept' => 'application/json' ]
  ]
);
$statusCode = $resp->getStatusCode();
if ($statusCode == 200) { //处理成功
  echo "success,return body = " . $resp->getBody()->getContents()."\n";
} else if ($statusCode == 204) { //处理成功,无返回Body
  echo "success";
}
} catch (RequestException $e) {
// 进行错误处理
echo $e->getMessage()."\n";
if ($e->hasResponse()) {
  echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
}
return;
}

更多参数、响应详情及错误码请参见 同步服务订单信息API接口文档

3.2.10【服务端】申请交易账单

步骤说明:微信支付按天提供交易账单文件,商户可以通过该接口获取账单文件的下载地址

示例代码:

public void TradeBill() throws Exception {

  //请求URL
  URIBuilder uriBuilder = new URIBuilder("https://api.mch.weixin.qq.com/v3/bill/tradebill");
  uriBuilder.setParameter("bill_date", "2020-11-09");
  uriBuilder.setParameter("bill_type", "ALL");
  
  //完成签名并执行请求
  HttpGet httpGet = new HttpGet(uriBuilder.build());
  httpGet.addHeader("Accept", "application/json");
  CloseableHttpResponse response = httpClient.execute(httpGet);
  
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) {
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'GET',
      'https://api.mch.weixin.qq.com/v3/bill/tradebill?bill_date=2019-06-11&sub_mchid=1900000001&bill_type=ALL', //请求URL
      [
          'headers' => [ 'Accept' => 'application/json']
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
} catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
}

更多参数、响应详情及错误码请参见 申请交易账单API接口文档

3.2.11【服务端】下载交易账单

步骤说明:通过申请交易账单接口获取到账单下载地址(download_url)后,再通过该接口获取到对应的账单文件,文件内包含交易相关的金额、时间、营销等信息,供商户核对订单、退款、银行到账等情况

示例代码

public void DownloadUrl(String download_url) throws Exception{
  PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new ByteArrayInputStream(privateKey.getBytes("utf-8")));
    
  //初始化httpClient
  //该接口无需进行签名验证、通过withValidator((response) -> true)实现
  httpClient =  WechatPayHttpClientBuilder.create().withMerchant(mchId, mchSerialNo, merchantPrivateKey).withValidator((response) -> true).build();
    
  //请求URL
  //账单文件的下载地址的有效时间为30s
  URIBuilder uriBuilder = new URIBuilder(download_url);
  HttpGet httpGet = new HttpGet(uriBuilder.build());
  httpGet.addHeader("Accept", "application/json");
    
  //执行请求
  CloseableHttpResponse response = httpClient.execute(httpGet);
  try {
      int statusCode = response.getStatusLine().getStatusCode();
      if (statusCode == 200) {
          System.out.println("success,return body = " + EntityUtils.toString(response.getEntity()));
      } else if (statusCode == 204) {
          System.out.println("success");
      } else {
          System.out.println("failed,resp code = " + statusCode+ ",return body = " + EntityUtils.toString(response.getEntity()));
          throw new IOException("request failed");
      }
  } finally {
      response.close();
  }
}
try {
  $resp = $client->request(
      'GET',
      'https://api.mch.weixin.qq.com/v3/billdownload/file?token=xx', //请求URL
      [
          'headers' => [ 'Accept' => 'application/json']
      ]
  );
  $statusCode = $resp->getStatusCode();
  if ($statusCode == 200) { //处理成功
      echo "success,return body = " . $resp->getBody()->getContents()."\n";
  } else if ($statusCode == 204) { //处理成功,无返回Body
      echo "success";
  }
  } catch (RequestException $e) {
  // 进行错误处理
  echo $e->getMessage()."\n";
  if ($e->hasResponse()) {
      echo "failed,resp code = " . $e->getResponse()->getStatusCode() . " return body = " . $e->getResponse()->getBody() . "\n";
  }
  return;
  }

注意:

  • 账单文件的下载地址的有效时间为30s
  • 强烈建议商户将实际账单文件的哈希值和之前从接口获取到的哈希值进行比对,以确认数据的完整性

更多参数、响应详情及错误码请参见 下载账单API接口文档

3.2.12【服务端】支付成功回调通知

步骤说明:当用户完成支付,微信会把相关支付结果将通过异步回调的方式通知商户,商户需要接收处理,并按文档规范返回应答。

注意:

  • 支付结果通知是以POST 方法访问商户设置的通知url,通知的数据以JSON 格式通过请求主体(BODY)传输。通知的数据包括了加密的支付结果详情
  • 加密不能保证通知请求来自微信。微信会对发送给商户的通知进行签名,并将签名值放在通知的HTTP头Wechatpay-Signature。商户应当验证签名,以确认请求来自微信,而不是其他的第三方。签名验证的算法请参考微信支付API v3签名方案
  • 支付通知http应答码为200或204才会当作正常接收,当回调处理异常时,应答的HTTP状态码应为500,或者4xx
  • 商户成功接收到回调通知后应返回成功的http应答码为200或204
  • 同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。 推荐的做法是,当商户系统收到通知进行处理时,先检查对应业务数据的状态,并判断该通知是否已经处理。如果未处理,则再进行处理;如果已处理,则直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
  • 如果在所有通知频率(4小时)后没有收到微信侧回调,商户应调用查询订单接口确认订单状态。

更多参数、响应详情及错误码请参见 支付成功回调通知API接口文档

3.2.13【客户端】跳转订单详情

微信支付根据用户不同的使用场景(APP、小程序、微信外H5)分别提供了对应跳转订单详情页的方法,请根据场景进行选择,详见跳转订单详情页API文档

4. 常见问题

Q:需确认订单模式,同一个用户可以下多少笔免押订单,如果该用户有未完结的支付分订单,会不会影响用户下一笔订单免押

A:同一个用户可以确认免押订单进行中的笔数是3笔(未完结&未支付的订单笔数,产品策略,可能会调整);如果用户进行中的订单超过3笔,则需要进行押金支付,如果有任何1笔待支付的订单,都是需要去支付押金的。

Q:商户小程序跳转支付分小程序(详情页,确认页)报错“未通过申请,当前服务未上线”

A:检查测试微信是否开通白名单,提供服务id和微信号联系运营开通白名单

Q:创建支付分订单返回{"code":"NO_AUTH","message":"商户暂无权限使用此服务"}

A:① 检查商户号和appid是否配置了通用化接口权限,可以联系微信侧运营确认和配置通用化接口权限

② 如果商户开通的是免确认订单权限,创建订单时need_user_confirm只能传false,如果商户开通的是含确认订单权限,创建订单时need_user_confirm只能传true

创建支付分订单返回{"code":"PARAM_ERROR","message":"订单风险金额名称不符合要求"}

A:检查商户号开通的是哪种模式的权限,需确认模式只能使用先免模式,免确认模式只能使用先享模式模式

Q:调用支付分创单接口报错返回“mch_id和appid未绑定”如何处理?

A:请商户自行检查mch_id和appid是否有对应的绑定关系。绑定步骤参考“商家商户号与AppID账号关联管理”。



技术咨询

文档反馈