一、前言
境外微信支付的实现方式,与国内支付的方式大不同,即不是V2,也不是V3,而是经由海外支付服务商,通过间连的方式来实现的。(香港地区融合钱包除外)。
以下专门介绍具体的实现攻略:
二、开通
首先,你需要先了解当地有哪些第三方支付服务商,然后选择其中一家,接入他们提供的支付服务,从而实现微信支付的开通。
1、全球第三方支付服务商:
https://pay.weixin.qq.com/index.php/public/wechatpay_en/partner_search#/
找到当地服务商的联系方式,与他们联系,他们会告诉你所需提供哪些资料,并帮你开通商户号,接入微信支付;
资料信息包括:
营业执照
翻译认证
公司银行账户
法人及联系人信息
其他信息等。
2、香港融合钱包
融合钱包以下不做介绍,技术方面和v3支付完全一样。
技术方案可以参考:
https://pay.weixin.qq.com/wiki/doc/api/wxpay/en/pages/MiniProgramPay_fw.shtml
三、第三方服务商提供的支付服务
每个第三方服务商所提供的支付服务各不相同,以下分别举例:
Yaband Pay (Europe PayPro B.V.):
微信支付, 支付宝, PayPal, SOFORT, IDEAL, 银联支付, List
2paynow (Europe Sepay B.V.):
微信支付, 支付宝
VR Payment (Europe CardProcess GmbH):
微信支付, 支付宝
Supay (Australia):
微信支付, 信用卡支付
其他地区第三方支付服务商,提供哪些服务,需要向他们咨询。
注:
1、境外小程序对除微信支付之外的支付方式,卡得并不象国内这样严,一般情况下,PayPal, 信用卡等方式,都支持并审核通过,支付宝会稍难点;
2、小程序里实现其他支付方式的唯一方案:生成支付链接,复制到剪贴板,在手机浏览器或微信浏览器中打开,完成支付流程。
四、第三方支付账号和接入文档
第三方帮你接入微信支付后,会回复邮件给你,并提供相关支付信息:
1. 一个属于第三方的支付账号
比如Yaband pay,支付账号类似如下:
ID: yourname@yoursite.com
Key: f4f70db6107xxxxxxxb75efa1f1490b4
2paynow,支付账号类似如下:
ID: 541102252
Key: osVx_0POxxxxF9h3JSCls
2. 该第三方支付服务商支付接入文档
比如Yaband pay 支付文档:
https://www.yabandmedia.com/api/cn-api.html
比如2paynow支付文档,是由技术支持人员发送给你的PDF文档,具体内容暂不介绍。
3. 第三方支付服务商的商家管理后台
你可以登录该后台进行配置,并查看详细的订单数据。
五、云开发实现境外支付
因为商家可以自主选择第三方支付服务商,所以我们在实现支付功能时,需要同时支付多个第三方支付服务商。
用云开发来实现,一般会有两种方式:
(1)、每个第三方的支付功能,分别用一个云函数实现;
(2)、一个云函数集成所有第三方支付功能;(我们采用的是该方式)
1、云函数pay.js:
按支付接口、支付服务商、支付方式分流:
const cloud = require('wx-server-sdk')
const rp = require('request-promise')
const CryptoJS = require('crypto-js')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
exports.main = async (event, context) => {
switch (event.action) {//根据小程序端传入的支付相关接口分流
case 'unifiedorder': return await unifiedorder(event)//统一下单
case 'queryorder': return await queryorder(event)//订单查询
case 'refund': return await refund(event)//退款
default: return 'wrong action'
}
}
async function unifiedorder(event) {
let res = await db.collection('config').doc('payment').get().catch()
if (res && res.data) { } else return 'wrong action'//注意:res?.data在云函数中不支持
let config = res.data//云数据库中保存了支付配置信息
switch (config.pay_sp) {//根据支付服务商pay_sp来分流
case '2paynow': return await unifiedorder_2paynow(event, config)
case 'yaband': return await unifiedorder_yaband(event, config)
case 'cloudpay': return await unifiedorder_cloudpay(event, config)
case 'supay': return await unifiedorder_supay(event, config)
case 'vr': return await unifiedorder_vr(event, config)
case 'v2': return await unifiedorder_v2(event, config)
case 'v3': return await unifiedorder_v3(event, config)
case 'v2sp': return await unifiedorder_v2(event, config)//v2服务商模式
case 'v3sp': return await unifiedorder_v3(event, config)//v3服务商模式
default: return 'wrong action'
}
}
async function queryorder(event) {
let res = await db.collection('config').doc('payment').get().catch()
if (res && res.data) { } else return 'wrong action'
let config = res.data
switch (config.pay_sp) {
case '2paynow': return await queryorder_2paynow(event, config)
case 'supay': return await queryorder_supay(event, config)
case 'vr': return await queryorder_vr(event, config)
default: return 'wrong action'
}
}
async function refund(event) {
let res = await db.collection('config').doc('payment').get()
if (res && res.data) { } else return 'wrong action'
let config = res.data
switch (res.data.pay_sp) {
case '2paynow': return await refund_2paynow(event, config)
case 'yaband': return await refund_yaband(event, config)
case 'cloudpay': return await refund_cloudpay(event, config)
case 'supay': return await refund_supay(event, config)
case 'vr': return await refund_vr(event, config)
default: return 'wrong action'
}
}
2、统一下单(Yaband pay为例)
接口文档:https://www.yabandmedia.com/api/cn-api.html
// -------------------yaband-----------------------------------------------------------
async function unifiedorder_yaband(event, config) {
let notify_url = config.notify_url
let user = config.mchid
let key = config.key
let timeStamp = parseInt(Date.now() / 1000)
let payMethod = event.pay_method
if (payMethod == 'wechat') {//微信支付
const wxContext = cloud.getWXContext()
let param = {
"user": user,
"method": "v3.CreatePaymentsWechatMiniPay",
"time": timeStamp,
}
let data = {
"pay_method": "online",
"sub_pay_method": "WeChat Pay",
"order_id": event.out_trade_no,
"amount": event.total_fee || "0.1",
"currency": event.fee_type || config.currency,
"description": event.body || "YabandPay pay",
"demo": event.demo || "order",
"timeout": "0",
"notify_url": notify_url,
"sub_app_id": wxContext.FROM_APPID || wxContext.APPID,
"sub_open_id": wxContext.FROM_OPENID || wxContext.OPENID
}
let sign = getSign_yaband({ ...param, ...data }, key)
let body = { ...param, sign, data }
let res = await rp({
url: "https://mapi.yabandpay.com/Payments",
method: 'POST',
body,
json: true
})
let payment = res.data.parameters
return { payment, sp_trade_no: res.data.trade_id }
}
if (payMethod == 'sofort') {//省略
}
if (payMethod == 'alipay') {//省略
}
if (payMethod == 'paypal') {//Paypal支付
let param = {
"user": user,
"method": "v3.CreatePayments",
"time": timeStamp,
}
let data = {
"pay_method": "online",
"sub_pay_method": "PAYPAL/RECURRING",
"order_id": event.out_trade_no,
"amount": event.total_fee || "0.1",
"currency": event.fee_type || config.currency,
"description": event.body || "YabandPay pay",
"demo": event.demo || "order",
"post_email": "xin.liu@elbsino.com",
"timeout": "0",
"redirect_url": "https://www.klarna.com/de/",
"notify_url": notify_url
}
let sign = getSign_yaband({ ...param, ...data }, key)
let body = { ...param, sign, data }
let res = await rp({
url: "https://mapi.yabandpay.com/Payments",
method: 'POST',
body,
json: true
})
let url = res.data.url
return { url }//除微信支付外,都必须返回一个url:支付链接
}
}
async function refund_yaband(event, config) {
console.log('refund_yaband')
let notify_url = config.notify_url
let user = config.mchid
let key = config.key
let timeStamp = parseInt(Date.now() / 1000)
let param = {
"user": user,
"method": "v3.CreateRefund",
"time": timeStamp,
}
let data = {
"trade_id": event.trade_id,
"refund_amount": event.refund_amount || "0.1",
"refund_currency": config.refund_currency,
"refund_description": event.refund_description || "test",
"notify_url": notify_url
}
let sign = getSign_yaband({ ...param, ...data }, key)
let body = { ...param, sign, data }
console.log(body)
return await rp({
url: "https://mapi.yabandpay.com/Payments",
method: 'POST',
body,
json: true
})
}
function getSign_yaband(args, key, sa = []) {//每个支付服务商的签名方式都不一样
for (let k in args) sa.push(k + '=' + args[k])
sa = sa.sort()
let signStr = sa.join('&')
let hash = CryptoJS.HmacSHA256(signStr, key)
return CryptoJS.enc.Hex.stringify(hash)
}
function getSign_2paynow(args, key) {//2paynow的签名算法。
let signStr = args.function + args.mid + args.timestamp + key
let hash = CryptoJS.MD5(signStr, key)
return CryptoJS.enc.Hex.stringify(hash)
}
踩坑实录:
1、每次统一下单,都会产生三种订单号:
商家订单号
支付服务商的支付订单号
微信支付订单号
记住:一定要保存支付服务商的支付订单号,因为有些支付服务商在订单查询和退款接口中,只认该订单号。
2、每个服务商的签名方案是不同的,有的甚至不需要签名,这个需要分别对待。
六、支付成功异步通知
多个服务商的异步通知,用一个云函数来实现。
云函数:notify.js
const cloud = require('wx-server-sdk')
cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })
const db = cloud.database()
const _ = db.command
exports.main = async (event, context) => {
let res = await db.collection('config').doc('payment').get()
switch (res.data.pay_sp) {
case '2paynow': return await notify_2paynow(event)
case 'yaband': return await notify_yaband(event)
case 'cloudpay': return await notify_cloudpay(event)
case 'vr': return await notify_vr(event)//vr暂时不支持回调通知
case 'supay': return await notify_supay(event)
case 'v2': return await notify_v2(event)
case 'v3': return await notify_v3(event)
default: return 'wrong action'
}
}
async function notify_2paynow(event) {
console.log('notify_2paynow')
let qs = event.queryStringParameters
if (qs.merchant_trade_no) { } else return 'success'
await db.collection('payment').add({ data: qs }).then(res => console.log(res))
if (qs.merchant_trade_no && qs.trade_status == 'TRADE_SUCCESS') {
await onPaymentSuccess(qs.merchant_trade_no, qs.trade_no, qs.original_trade_no)
}
return 'success'
}
async function notify_yaband(event) {
console.log('notify_yaband')
if (event.body) { } else return { statusCode: 200, body: 'ok' }
let body = JSON.parse(event.body)
let pay = body.data
await db.collection('payment').add({ data: pay }).then(res => console.log(res))
if (pay.transaction_id && pay.state == 'paid') {
await onPaymentSuccess(event.outTradeNo)
}
return { statusCode: 200, body: 'ok' }
}
async function notify_cloudpay(event) {//云支付pay_cb
console.log('notify_cloudpay')
if (event.outTradeNo) {
await db.collection('payment').add({ data: event }).then(res => console.log(res))
if (event.returnCode == 'SUCCESS' && event.resultCode == 'SUCCESS') {
await onPaymentSuccess(event.outTradeNo)
}
}
return { "errcode": 0, "errmsg": 'SUCCESS' }
}
踩坑实录:
1、有些国外支付服务商,并不提供异步通知功能,比如VR Payment,这就很尴尬了,因为我们业务的流程里,是非常依赖这个功能的,因此,后期造成了大量的代码修改工作,建议大家从一开始就考虑这种情况。
七、币种选择
统一下单时,支持两种币种:
1、CNY
2、当地币种,比如EUR
境外退款,一般只能使用当地币退款,不支持RMB。
八、境外小程序云开发禁用后的选择
目前境外小程序的云开发功能已经被禁用,可以通过环境共享的模式支持以上云开发境外支付方案;
1、共享在禁用令之前的同主体小程序的云环境;
2、我们是采用开放平台第三方服务商批量云开发模式;
九、境外小程序相关文章
1、https://developers.weixin.qq.com/community/develop/article/doc/000aec921e4fd8a320ec0f9795bc13
2、https://developers.weixin.qq.com/community/develop/article/doc/0000e805af0900b37f6c900c356c13
3、https://developers.weixin.qq.com/community/develop/article/doc/000a68f1a24a687242eb2427556013
4、https://developers.weixin.qq.com/community/develop/article/doc/000860b434cd180a749b9268f51c13
1 境外支付是否有支付服务商这种概念?
2 业务场景为集团小程序,里面有单店,需要收集到单店自己账户,是集团和第三方签约还是单店和第三方签约?
2、集团和单店都只能是特约商户,和两个不同商家一样对待。
麻烦请问一下老张,国内主体的小程序,支持开通境外微信支付吗?
国内主体的小程序,在国外使用。
1 小程序内调起微信支付,在境内和境外的是否有区别?
2 使用官方V3文档在国内开发完毕,在国外是否无须修改即可使用。
感谢!
2、除了香港融合钱包,v3基本无用,完全重新开发。
请问下老张,现在国外小程序,要接入谷歌支付和银行卡支付,能不能直接在小程序内发起支付,不走浏览器
请问老张
1、支付方式是微信时,小程序上是用wx.requestPayment调起原生支付么?
2、支付方式是其他时,返回的url可以用webview组件打开支付么,您现在是如何使用url方式支付的?
2、不能用webview打开,非法业务域名。
请问老张:境外小程序现在支付是否不需要对接当地第三方支付服务商,可以直接只用微信支付功能。感谢!!!
请问老张是否了解:
谢谢!!
1. 海外实体(澳门)注册的小程序,可以同时被中国和澳门居民搜到吗?
2. 海外实体注册海外小程序/公众号后,在微信生态投广告会受到哪些限制?如澳门的小程序也可以在朋友圈/公众号/banner位投放广告吗?
3. 海外(澳门)单主体公众号能否根据浏览者当前位置的不同从而显示不同的底部功能菜单?
感谢老张!
中国的公众号,能否按照浏览者的位置不同,设置不同的底部自定义菜单?如澳门的浏览者看到的公众号底部菜单是abd,中国内地浏览者是cdf?
境外主体面对国内用户的小程序也适用吗?境外主体可否上线含支付功能的教育或工具小程序,需要哪些资质认证?谢谢
2、具体看文档:https://developers.weixin.qq.com/miniprogram/product/material/#境外主体小程序开放的服务类目
麻烦问下,境内个人有什么方法汇款境外的公司吗