├── .gitignore ├── README.md ├── app.js ├── cloud.js ├── config └── alipay.json ├── package.json ├── public └── index.html ├── routes └── pay.js ├── server.js └── utils └── alipay.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | node_modules/ 3 | 4 | .avoscloud/ 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 注意 2 | 3 | 因支付宝接口变动频繁,该项目不再维护,代码可能无法正常工作。 4 | 5 |
6 | 以下为原 README 内容 7 | 8 | ## 了解支付宝「即时到账收款」 9 | 10 | 在尝试该项目之前,你需要了解支付宝「即时到账收款」这个功能: 11 | 12 | * 相关的介绍在这里:https://b.alipay.com/order/productDetail.htm?productId=2012111200373124 13 | * 确认自己有「企业支付宝账号(不含个体工商户)」 14 | * 了解整个流程是什么样子的 15 | 16 | 然后就可以继续了。 17 | 18 | ## 安装 19 | 20 | ### 下载代码: 21 | 22 | ``` 23 | git clone git@github.com:leancloud/cloud-code-alipay.git 24 | ``` 25 | 26 | ### 修改支付宝相关的配置 `config/alipay.json` 27 | 28 | ``` 29 | { 30 | "sign_type": "MD5", 31 | "alipay_gateway": "https://mapi.alipay.com/gateway.do?", 32 | "https_verify_url": "https://mapi.alipay.com/gateway.do?service=notify_verify&", 33 | "partner": "2088000000000000", 34 | "key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 35 | "notify_url": "http://xxx.avosapps.com/pay/notify", 36 | "return_url": "http://xxx.avosapps.com/pay/return" 37 | } 38 | ``` 39 | * alipay_gateway: 支付宝网关,一般情况默认即可。 40 | * https_verify_url: 支付宝回调验证 url,一般情况默认即可。 41 | * partner: 合作者身份(PID),2088 开头的 16 位数字,可以在支付宝网站查找: https://b.alipay.com/order/pidAndKey.htm 42 | * key: 安全校验码,数字加字母的字符串,可以在支付宝网站查找: https://b.alipay.com/order/pidAndKey.htm 43 | * notify_url: 支付宝异步通知 url,二级域名根据 LeanEngine 配置而定。 44 | * return_url: 支付宝同步通知 url,二级域名根据 LeanEngine 配置而定。 45 | 46 | ### 部署 47 | 48 | 配置 LeanEngine appId 和 appKey 49 | 50 | ``` 51 | avoscloud app add 52 | ``` 53 | 54 | 切换目标应用 55 | 56 | ``` 57 | avoscloud checkout 58 | ``` 59 | 60 | 部署应用到测试环境和生产环境 61 | 62 | ``` 63 | avoscloud deploy && avoslcoud publish 64 | ``` 65 | 66 | **提示**: 过程中可能会提示输入 masterKey。 67 | 68 | 如果没有错误,请打开浏览器,根据自己的二级域名键入网址: `http://.avosapps.com` 69 | 70 | 如果看到「支付宝即时到账交易接口」的页面,恭喜你,部署成功! 71 | 72 | ## 感受一下 73 | 74 | 1. 在「支付宝即时到账交易接口」的测试页面,输入相关信息。**注意**:「卖家支付宝用户号」需要和「partner」一致;金额可以输入 0.01 (表示支付 1 分钱)来进行尝试。输入完成后点击确认。 75 | 2. 你将看到跳转到支付宝页面,输入自己的支付宝账号和支付密码等完成支付。 76 | 3. 支付完成后会跳转回我们自己的应用页面,并显示 `验证结果:true`。支付流程结束。你可以到后台日志中看到支付宝的回调。当然,你的 1 分钱也转到了对应的卖家账户 ;) 77 | 78 | ## 开发相关 79 | 80 | ### 文件说明 81 | 82 | * `routes/pay.js`: 支付相关请求路由。 83 | * `utils/alipay.js`: 支付宝相关签名验证,生成跳转等逻辑。 84 | 85 | ### 路由信息 86 | 87 | * `GET /`: 静态首页 `public/index.html`。 88 | * `POST /pay`: 接受表单信息、签名,并准备跳转到支付宝。 89 | * `GET /pay/return`: 等待支付宝同步回调,并验证调用方是否真正来自支付宝。 90 | * `POST /pay/notify`:等待支付宝异步回调,并验证调用方是否真正来自支付宝。 91 |
92 | 93 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var express = require('express'); 3 | var bodyParser = require('body-parser'); 4 | 5 | var cloud = require('./cloud'); 6 | var alipay = require('./routes/pay'); 7 | 8 | var app = express(); 9 | 10 | app.use(express.static('public')); 11 | 12 | // 加载云代码方法 13 | app.use(cloud); 14 | 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ extended: false })); 17 | 18 | // 可以将一类的路由单独保存在一个文件中 19 | app.use('/pay', alipay); 20 | 21 | // 如果任何路由都没匹配到,则认为 404 22 | // 生成一个异常让后面的 err handler 捕获 23 | app.use(function(req, res, next) { 24 | var err = new Error('Not Found'); 25 | err.status = 404; 26 | next(err); 27 | }); 28 | 29 | // error handlers 30 | 31 | // 如果是开发环境,则将异常堆栈输出到页面,方便开发调试 32 | if (app.get('env') === 'development') { 33 | app.use(function(err, req, res, next) { // jshint ignore:line 34 | res.status(err.status || 500); 35 | res.send({ 36 | message: err.message, 37 | error: err 38 | }); 39 | }); 40 | } 41 | 42 | // 如果是非开发环境,则页面只输出简单的错误信息 43 | app.use(function(err, req, res, next) { // jshint ignore:line 44 | res.status(err.status || 500); 45 | res.send({ 46 | message: err.message, 47 | error: {} 48 | }); 49 | }); 50 | 51 | module.exports = app; 52 | -------------------------------------------------------------------------------- /cloud.js: -------------------------------------------------------------------------------- 1 | var AV = require('leanengine'); 2 | 3 | /** 4 | * 一个简单的云代码方法 5 | */ 6 | AV.Cloud.define('hello', function(request, response) { 7 | response.success('Hello world!'); 8 | }); 9 | 10 | module.exports = AV.Cloud; 11 | -------------------------------------------------------------------------------- /config/alipay.json: -------------------------------------------------------------------------------- 1 | { 2 | "sign_type": "MD5", 3 | "alipay_gateway": "https://mapi.alipay.com/gateway.do?", 4 | "https_verify_url": "https://mapi.alipay.com/gateway.do?service=notify_verify&", 5 | "partner": "2088000000000000", 6 | "key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 7 | "notify_url": "http://xxx.avosapps.com/pay/notify", 8 | "return_url": "http://xxx.avosapps.com/pay/return" 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "leanengine-alipay-test", 3 | "description": "LeanEngine Alipay test project.", 4 | "version": "0.0.2", 5 | "private": true, 6 | "dependencies": { 7 | "body-parser": "1.12.3", 8 | "debug": "^2.1.1", 9 | "express": "4.12.3", 10 | "leanengine": "^0.1.4", 11 | "request": "^2.58.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 支付宝即时到账交易接口 4 | 5 | 6 | 7 |
8 |

支付宝即时到账交易接口快速通道

9 |

以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。

10 |
11 | 12 | 13 | 14 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 32 | 33 | 34 | 35 | 39 | 40 | 41 | 42 | 45 | 46 | 47 | 48 | 52 |
商户订单号: 15 | 16 | 商户网站订单系统中唯一订单号,必填 17 |
订单名称: 22 | 23 | 必填 24 |
卖家支付宝用户号: 29 | 30 | 以 2088 开头的纯 16 位数字,必填 31 |
付款金额: 36 | 37 | 必填 38 |
订单描述: 43 | 44 |
商品展示地址: 49 | 50 | 需以http://开头的完整路径,例如:http://www.xxx.com/myorder.html 51 |
53 | 54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /routes/pay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var router = require('express').Router(); 3 | var alipay = require('../utils/alipay'); 4 | 5 | router.post('/', function(req, res) { 6 | var params = { 7 | //商户网站订单系统中唯一订单号,必填 8 | out_trade_no: req.body.out_trade_no, 9 | //订单名称,必填 10 | subject: req.body.subject, 11 | //卖家支付宝帐户,必填 12 | seller_id: req.body.seller_id, 13 | // 付款金额,必填 14 | total_fee: req.body.total_fee, 15 | // 订单描述 16 | body: req.body.body, 17 | // 商品展示地址 18 | //需以http://开头的完整路径,例如:http://www.商户网址.com/myorder.html 19 | show_url: req.body.show_url 20 | }; 21 | var html = alipay.getDirectPayReqHtml(params, 'get'); 22 | res.send(html); 23 | }); 24 | 25 | router.get('/return', function(req, res) { 26 | console.log('return query: ', req.query); 27 | alipay.verify(req.query, function(err, result) { 28 | console.log('result: ', err, result); 29 | if (err) { 30 | return res.send('err: ' + err); 31 | } 32 | return res.send('验证结果: ' + result); 33 | }); 34 | }); 35 | 36 | router.post('/notify', function(req, res) { 37 | console.log('notify params:', req.params); 38 | alipay.verify(req.params, function(err, result) { 39 | console.log('result: ', err, result); 40 | if (err) { 41 | return res.send('err: ' + err); 42 | } 43 | return res.send('验证结果: ' + result); 44 | }); 45 | }); 46 | 47 | module.exports = router; 48 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var AV = require('leanengine'); 3 | 4 | var APP_ID = process.env.LC_APP_ID; 5 | var APP_KEY = process.env.LC_APP_KEY; 6 | var MASTER_KEY = process.env.LC_APP_MASTER_KEY; 7 | 8 | AV.initialize(APP_ID, APP_KEY, MASTER_KEY); 9 | 10 | var app = require('./app'); 11 | 12 | // 端口一定要从环境变量 `LC_APP_PORT` 中获取。 13 | // LeanEngine 运行时会分配端口并赋值到该变量。 14 | var PORT = parseInt(process.env.LC_APP_PORT || 3000); 15 | app.listen(PORT, function () { 16 | console.log('Node app is running, port:', PORT); 17 | }); 18 | -------------------------------------------------------------------------------- /utils/alipay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var crypto = require('crypto'); 3 | var debug = require('debug')('AV:alipay'); 4 | var request = require('request'); 5 | 6 | var config = require('../config/alipay.json'); 7 | 8 | var defaultParams = { 9 | service: 'create_direct_pay_by_user', 10 | partner: config.partner, 11 | '_input_charset': 'utf-8', 12 | //支付类型,必填,不能修改 13 | payment_type: '1', 14 | notify_url: config.notify_url, 15 | return_url: config.return_url 16 | }; 17 | 18 | // 生成及时到账交易请求 html 19 | exports.getDirectPayReqHtml = function(params, strMethod) { 20 | var finalParams = JSON.parse(JSON.stringify(defaultParams)); 21 | for (var k in params) { 22 | finalParams[k] = params[k]; 23 | } 24 | debug('params: %j', finalParams); 25 | var result = '
'; 28 | var reqParams = buildRequestPara(finalParams); 29 | for (k in reqParams) { 30 | result += '
'; 31 | } 32 | //submit按钮控件请不要含有name属性 33 | result += ''; 34 | debug('generate request html:', result); 35 | return result; 36 | }; 37 | 38 | // 验证消息是否是支付宝发出的合法消息 39 | exports.verify = function(params, cb) { 40 | //判断responsetTxt是否为true 41 | //responsetTxt的结果不是true,与服务器设置问题、合作身份者ID、notify_id一分钟失效有关 42 | if(!params.notify_id) { 43 | debug('verify err: notify_id is null.'); 44 | return cb(null, false); 45 | } 46 | verifyResponse(params.notify_id, function(err, result) { 47 | if (err || !result) { 48 | debug('verify err:', err || result); 49 | return cb(err, false); 50 | } 51 | //判断 verifySign 是否为true 52 | //如果不是true,与安全校验码、请求时的参数格式(如:带自定义参数等)、编码格式有关 53 | if (verifySign(params, params.sign || '')) { 54 | return cb(null, true); 55 | } 56 | return cb(null, false); 57 | }); 58 | }; 59 | 60 | // 获取远程服务器ATN结果,验证返回URL 61 | var verifyResponse = function(notifyId, cb) { 62 | var verifyUrl = config.https_verify_url + 'partner=' + config.partner + "¬ify_id=" + notifyId; 63 | debug(verifyUrl); 64 | request(verifyUrl, function(err, res, body) { 65 | if (err) { 66 | return cb(err, false); 67 | } 68 | if (res.statusCode != 200) { 69 | debug('verify err:', body); 70 | return cb(null, false); 71 | } 72 | debug('verify result:', body); 73 | cb(null, body === 'true'); 74 | }); 75 | }; 76 | 77 | var verifySign = function(params, sign) { 78 | if (config.sign_type === 'MD5') { 79 | var paramsStr = createLinkString(paraFilter(params)); 80 | var mySign = crypto.createHash('md5').update(paramsStr + config.key, 'utf-8').digest('hex'); 81 | return sign == mySign; 82 | } 83 | return false; 84 | }; 85 | 86 | var buildRequestPara = function(params) { 87 | var reqParams = paraFilter(params); 88 | var paramsStr = createLinkString(reqParams); 89 | if (config.sign_type === 'MD5') { 90 | reqParams.sign = crypto.createHash('md5').update(paramsStr + config.key, 'utf-8').digest('hex'); 91 | debug('build request params: paramsString=' + paramsStr + ', sign=' + reqParams.sign); 92 | reqParams.sign_type = config.sign_type; 93 | } else { 94 | reqParams.sign = ''; 95 | } 96 | return reqParams; 97 | }; 98 | 99 | // 除去数组中的空值和签名参数 100 | var paraFilter = function(params) { 101 | var result = {}; 102 | if (!params) { 103 | return result; 104 | } 105 | for (var k in params) { 106 | if (!params[k] || params[k] === '' || k === 'sign' || k === 'sign_type') { 107 | continue; 108 | } 109 | result[k] = params[k]; 110 | } 111 | return result; 112 | }; 113 | 114 | // 将所有参数按照“参数=参数值”的模式用“&”字符拼接成字符串 115 | var createLinkString = function(params) { 116 | var result = ''; 117 | var sortKeys = Object.keys(params).sort(); 118 | for (var i in sortKeys) { 119 | result += sortKeys[i] + '=' + params[sortKeys[i]] + '&'; 120 | } 121 | if (result.length > 0) { 122 | return result.slice(0, -1); 123 | } else { 124 | return result; 125 | } 126 | }; 127 | --------------------------------------------------------------------------------