评论

境外小程序支付攻略(云开发篇),看这一篇就够了。

[证文活动]境外小程序如何实现支付功能,看这一篇就足够了。

一、前言

境外微信支付的实现方式,与国内支付的方式大不同,即不是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



最后一次编辑于  2022-05-31  
点赞 4
收藏
评论

13 个评论

  • 岁月如歌
    岁月如歌
    发表于移动端
    2022-05-18
    3ffvvv
    2022-05-18
    赞同
    回复
  • 岁月如歌
    岁月如歌
    发表于移动端
    2022-05-18
    mkm.fjjhn. . .i b(´∇ノ`*)ノ
    2022-05-18
    赞同
    回复
  • 老张
    老张
    2022-05-16

    大家还有哪些关于境外支付要了解的内容,请留言,我来补充。

    2022-05-16
    赞同
    回复 1
    • 锦荣
      锦荣
      2022-10-08
      国内人民币支付,可以用香港商户主体收款吗,收款为港币
      2022-10-08
      回复

正在加载...

登录 后发表内容