Scenario introduction
It is applicable for merchants to integrate wechat payment function in mobile APP.
When the merchant APP calls the SDK provided by wechat to call the wechat payment module, the merchant APP will jump to wechat to complete the payment, then jump back to the merchant APP after the payment, and finally display the payment results.
At present, wechat payment support mobile systems include IOS (Apple), Android (Android) and WP (Windows Phone).
Document reference
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=8_1
Development steps
1. Generate client payment parameters
Call the unified single interface to obtain the pre authorization id, and use the pre authorization id to sign twice to generate the parameters required by the client to start wechat payment
2. Notice of payment result
After the payment is completed, wechat will send the relevant payment results and user information to the merchant, which needs to receive and process and return the response.
When interacting with back-end notifications, if wechat receives a response from a merchant that is not successful or overtime, wechat considers the notification as a failure, wechat will periodically re launch the notification through a certain strategy to improve the success rate of the notification as much as possible, but wechat does not guarantee that the notification will eventually succeed.
Original wechat app payment SDK
Note: official didn't provide
<?php
namespace app\pay\tool;
/**
* Place an order on wechat payment server
* @author lzw
* Wechat APP payment document address: https://pay.weixin.qq.com/wiki/doc/api/APP.php? Chapter = 8_
* Use example
* $options = array(
* 'appid' => '**********', //Fill in the public account ID assigned by wechat
* 'mchid' => '********', //Fill in the merchant number assigned by wechat payment
* 'notify_url'=> 'http://www.baidu.com/', //Fill in wechat payment result callback address
* 'key' => ''**********'' //Fill in wechat merchant payment key
* );
* Unified order method
* $WechatAppPay = new wechatAppPay($options);
* $params['body'] = 'Product description '; / / product description
* $params['out_trade_no'] = '1217752501201407'; //Custom order number
* $params['total_fee'] = '100'; //The order amount can only be an integer with the unit of cent
* $wechatAppPay->unifiedOrder( $params );
*/
class WxpayAppSDK
{
//Interface API URL prefix
const API_URL_PREFIX = 'https://api.mch.weixin.qq.com';
//Order address URL
const UNIFIEDORDER_URL = "/pay/unifiedorder";
//Query order URL
const ORDERQUERY_URL = "/pay/orderquery";
//Close order URL
const CLOSEORDER_URL = "/pay/closeorder";
//Public account ID
private $appid;
//Merchant No
private $mch_id;
//Random string
private $nonce_str;
//autograph
private $sign;
//Product description
private $body;
//Merchant order number
private $out_trade_no;
//Total amount paid
private $total_fee;
//Terminal IP
private $spbill_create_ip;
//Payment result callback notification address
private $notify_url;
//Transaction type
private $trade_type;
//Payment key
private $key;
//Certificate path
private $SSLCERT_PATH;
private $SSLKEY_PATH;
// Save error message
public $errorMsg = '';
//All parameters
private $params = array();
/**
* Incoming configuration information
* $options = array(
* 'appid' => '**********', //Fill in the public account ID assigned by wechat
* 'mchid' => '********', //Fill in the merchant number assigned by wechat payment
* 'notify_url'=> 'http://www.baidu.com/', //Fill in wechat payment result callback address
* 'key' => ''**********'' //Fill in wechat merchant payment key
* );
* WxpayApp constructor.
* @param $options
*/
public function __construct($options)
{
$this->appid = isset($options['appid']) ? $options['appid'] : '';
$this->mch_id = isset($options['mchid']) ? $options['mchid'] : '';
$this->notify_url = isset($options['notify_url']) ? $options['notify_url'] : '';
$this->key = isset($options['key']) ? $options['key'] : '';
}
/**
* Order method - > unified order interface
* @link https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1
* @param $params Order parameters
*/
public function unifiedOrder($params)
{
$this->body = $params['body'];
$this->out_trade_no = $params['out_trade_no'];
$this->total_fee = $params['total_fee'];
$this->trade_type = 'APP';//Transaction type JSAPI | NATIVE |APP | WAP
$this->nonce_str = $this->genRandomString();
$this->spbill_create_ip = $_SERVER['REMOTE_ADDR'];
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->nonce_str;
$this->params['body'] = $this->body;
$this->params['out_trade_no'] = $this->out_trade_no;
$this->params['total_fee'] = $this->total_fee;
$this->params['spbill_create_ip'] = $this->spbill_create_ip;
$this->params['notify_url'] = $this->notify_url;
$this->params['trade_type'] = $this->trade_type;
//Get signature data
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::UNIFIEDORDER_URL);
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
if (!empty($result['result_code']) && !empty($result['err_code'])) {
$result['err_msg'] = $this->error_code($result['err_code']);
}
return $result;
}
/**
* Query order information
* @param $out_trade_no order number
* @return array
*/
public function orderQuery($out_trade_no)
{
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->genRandomString();
$this->params['out_trade_no'] = $out_trade_no;
//Get signature data
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::ORDERQUERY_URL);
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
if (!empty($result['result_code']) && !empty($result['err_code'])) {
$result['err_msg'] = $this->error_code($result['err_code']);
}
return $result;
}
/**
* Close order
* @param $out_trade_no order number
* @return array
*/
public function closeOrder($out_trade_no)
{
$this->params['appid'] = $this->appid;
$this->params['mch_id'] = $this->mch_id;
$this->params['nonce_str'] = $this->genRandomString();
$this->params['out_trade_no'] = $out_trade_no;
//Get signature data
$this->sign = $this->MakeSign($this->params);
$this->params['sign'] = $this->sign;
$xml = $this->data_to_xml($this->params);
$response = $this->postXmlCurl($xml, self::API_URL_PREFIX . self::CLOSEORDER_URL);
if (!$response) {
return false;
}
$result = $this->xml_to_data($response);
return $result;
}
/**
*
* Get payment result notification data
* return array
*/
public function getNotifyData()
{
//Get notified data
$xml = $GLOBALS['HTTP_RAW_POST_DATA'];
$data = array();
if (empty($xml)) {
return false;
}
$data = $this->xml_to_data($xml);
if (!empty($data['return_code'])) {
if ($data['return_code'] == 'FAIL') {
return false;
}
}
return $data;
}
/**
* Verify notification signature
*/
public function verifyNotify($data)
{
$sign = $data['sign'];
unset($data['sign']);
if ($sign != $this->MakeSign($data)) {
return false;
} else {
return true;
}
}
/**
* Reply and output XML data after receiving notification successfully
* @param string $xml
*/
public function replyNotifySuccess()
{
$data['return_code'] = 'SUCCESS';
$data['return_msg'] = 'OK';
$xml = $this->data_to_xml($data);
echo $xml;
die();
}
/**
* Reply and output XML data after receiving notification failure
* @param string $xml
*/
public function replyNotifyFail()
{
$data['return_code'] = 'Fail';
$data['return_msg'] = 'Failed to process order';
$xml = $this->data_to_xml($data);
echo $xml;
die();
}
/**
* The second signature is used by the client to call the wechat client
* @link https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_7&index=3
* Generate APP payment parameters
* @param $prepayid Advance payment id
*/
public function getAppPayParams($prepayid)
{
$data['appid'] = $this->appid;
$data['partnerid'] = $this->mch_id;
$data['prepayid'] = $prepayid;
$data['package'] = 'Sign=WXPay';
$data['noncestr'] = $this->genRandomString();
$data['timestamp'] = time();
$data['sign'] = $this->MakeSign($data);
return $data;
}
/**
* Get signature data to client quickly
* @param $param
* $params['body'] = 'Product description '; / / product description
* $params['out_trade_no'] = '1217752501201407'; //Custom order number
* $params['total_fee'] = '100'; //The order amount can only be an integer with the unit of cent
* @return mixed
*/
public function getAppPaySign($params)
{
$result = $this->unifiedOrder($params);
if ($result['return_code'] == 'SUCCESS' && $result['result_code'] == 'SUCCESS') {
$data = $this->getAppPayParams($result['prepay_id']);
return $data;
} else {
// $result ['return ﹐ MSG '] pay attention to the cause of tracking failure
$this->errorMsg = $result['return_msg'];
return false;
}
}
/**
* Generate signature
* @return autograph
*/
public function MakeSign($params)
{
//Signature step 1: sort array parameters in dictionary order
ksort($params);
$string = $this->ToUrlParams($params);
//Signature step 2: add KEY after string
$string = $string . "&key=" . $this->key;
//Signature step 3: MD5 encryption
$string = md5($string);
//Step 4: change all characters to uppercase
$result = strtoupper($string);
return $result;
}
/**
* Splicing parameters into URL: key = value & key = value
* @param $params
* @return string
*/
public function ToUrlParams($params)
{
$string = '';
if (!empty($params)) {
$array = array();
foreach ($params as $key => $value) {
$array[] = $key . '=' . $value;
}
$string = implode("&", $array);
}
return $string;
}
/**
* Output xml characters
* @param $params Parameter name
* return string Return the assembled xml
**/
public function data_to_xml($params)
{
if (!is_array($params) || count($params) <= 0) {
return false;
}
$xml = "<xml>";
foreach ($params as $key => $val) {
if (is_numeric($val)) {
$xml .= "<" . $key . ">" . $val . "</" . $key . ">";
} else {
$xml .= "<" . $key . "><![CDATA[" . $val . "]]></" . $key . ">";
}
}
$xml .= "</xml>";
return $xml;
}
/**
* Convert xml to array
* @param string $xml
* return array
*/
public function xml_to_data($xml)
{
if (!$xml) {
return false;
}
//Convert XML to array
//Prohibit reference to external xml entities
libxml_disable_entity_loader(true);
$data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
return $data;
}
/**
* Get timestamps at millisecond level
*/
private static function getMillisecond()
{
//Get timestamps in milliseconds
$time = explode(" ", microtime());
$time = $time[1] . ($time[0] * 1000);
$time2 = explode(".", $time);
$time = $time2[0];
return $time;
}
/**
* Generates a random string of a specified length and returns it to the user
* @param type $len Length of the resulting string
* @return string Random string
*/
private function genRandomString($len = 32)
{
$chars = array(
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R",
"S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2",
"3", "4", "5", "6", "7", "8", "9"
);
$charsLen = count($chars) - 1;
// Scramble array
shuffle($chars);
$output = "";
for ($i = 0; $i < $len; $i++) {
$output .= $chars[mt_rand(0, $charsLen)];
}
return $output;
}
/**
* Submit xml to the corresponding interface url by post
*
* @param string $xml xml data requiring post
* @param string $url url
* @param bool $useCert Certificate required or not, not required by default
* @param int $second url Execution timeout, default 30s
* @throws WxPayException
*/
private function postXmlCurl($xml, $url, $useCert = false, $second = 30)
{
$ch = curl_init();
//Set timeout
curl_setopt($ch, CURLOPT_TIMEOUT, $second);
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
//Set header
curl_setopt($ch, CURLOPT_HEADER, FALSE);
//The result is expected to be a string and output to the screen
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
if ($useCert == true) {
//Set up certificate
//Use certificate: cert and key belong to two. pem files respectively
curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'PEM');
//curl_setopt($ch,CURLOPT_SSLCERT, WxPayConfig::SSLCERT_PATH);
curl_setopt($ch, CURLOPT_SSLKEYTYPE, 'PEM');
//curl_setopt($ch,CURLOPT_SSLKEY, WxPayConfig::SSLKEY_PATH);
}
//post submission method
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
//Run curl
$data = curl_exec($ch);
//Return results
if ($data) {
curl_close($ch);
return $data;
} else {
$error = curl_errno($ch);
curl_close($ch);
return false;
}
}
/**
* error code
* @param $code Error code output by server
* return string
*/
public function error_code($code)
{
$errList = array(
'NOAUTH' => 'The merchant has not opened this interface authority',
'NOTENOUGH' => 'Insufficient balance of user account',
'ORDERNOTEXIST' => 'Order number does not exist',
'ORDERPAID' => 'Merchant order paid, no need to repeat operation',
'ORDERCLOSED' => 'The current order is closed and cannot be paid',
'SYSTEMERROR' => 'System error!System timeout',
'APPID_NOT_EXIST' => 'Missing in parameter APPID',
'MCHID_NOT_EXIST' => 'Missing in parameter MCHID',
'APPID_MCHID_NOT_MATCH' => 'appid and mch_id Mismatch',
'LACK_PARAMS' => 'Missing required request parameters',
'OUT_TRADE_NO_USED' => 'The same transaction cannot be submitted multiple times',
'SIGNERROR' => 'Incorrect parameter signature result',
'XML_FORMAT_ERROR' => 'XML Format error',
'REQUIRE_POST_METHOD' => 'not used post Pass parameters ',
'POST_DATA_EMPTY' => 'post Data cannot be empty',
'NOT_UTF8' => 'The specified encoding format is not used',
);
if (array_key_exists($code, $errList)) {
return $errList[$code];
}
}
}
Original address: https://www.kancloud.cn/weber_lzw/book/253125