介绍

PingPongCheckout 跨境支付的 API 接口文档,商户服务器和 PingPongCheckout 服务器进行交互。 供商户/平台服务方的技术开发及测试相关人员使用。 本文档分别从交互流程、通讯方式、签名方 案、交易接口、注意事项等⻆度详细介绍了 PingPongCheckout 跨境支付 API 接口的工作方式和开发过 程,可以帮助开发人员快速接入支付系统,同时也可以作为后续接口参数以及参数类型的速查手册。

Pingpong商户接入指南

收银台模式系统交互流程介绍

通俗一点收银台的付款方式就像现实生活中我们去饭店吃饭时一样,我们只需要去饭店的收银台给他钱然后至于如何扣款怎么扣款我们都不需要过多关心,而收银台付款也是这样,我们只需要把付款的参数交给对方至于具体扣款第三方支付平台会给我们返回一个他们的支付界面,然后我们引导用户过去进行支付操作,支付支付之后的结果我们需要提供一个回调函数,用于第三方支付平台告诉我们支付结果

对于整一个收银台的支付流程借鉴一下官方的图,说的很详细

步骤

  1. 客户端提交订单给商户服务端处理
  2. 商户服务端返回PingPongCheckout JS-SDK需要的参数(包含订单信息,签名和JS-SDK初始化需要的参数)
  3. 客户端初始化PingPongCheckout SDK,并且调用createPayment,传入订单信息。
  4. SDK自动开始和PingPongCheckout服务端交互,成功之后将会渲染PingPongCheckout收银台
  5. 买家填写卡号和cvv等支付信息
  6. 提交支付信息
  7. 如果是3D交易,还需要3D验证,否则直接展示交易结果
  • 图例:

  • 7.1 3D流程

  • 7.2 非3D流程

  1. 异步通知详见如何获取交易状态
  2. 收到异步通知需要响应OK给PingPongCheckout

此次对接的是“统一下单-本地支付&信用卡”方式支付,他与“获取跳转收银台”方式两者都是在请求完pingpong支付之后生成收银台的跳转链接,但是后者是由我们开发者控制界面跳转只返回支付的界面URL,而后者是由pingpong直接跳转过去,我们采用的是前者也就是统一下单-本地支付&信用卡方式

Coding

沙箱环境获取收银台VO类

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** PingPong支付充值获取支付收银台地址所需参数对象* SDK address:<a href="https://acquirer-api-docs-v3.pingpongx.com/pages/a2c224/#%E5%85%AC%E5%85%B1%E8%AF%B7%E6%B1%82%E5%8F%82%E6%95%B0">...</a>* 参数上没有写选传或者条件必传则代表字段为必传字段** @author Czw* @date 2023/05/05*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CoinRechargePingPongRequest implements Serializable {/*** 唯一请求号*/private String requestId;/*** PingPong 商户店铺编号*/private String accId;/*** MD5-使用MD5算法加签* SHA256-使用SHA256算法加签*/private String signType;/*** 签名,签名秘钥(salt)放入签名串的位置为: 签名串的开头 , 即{salt}key1=val2&key2=val2&key3=val3* <a href="https://acquirer-api-docs-v3.pingpongx.com/pages/77ae52/#%E8%AF%B7%E6%B1%82%E7%AD%BE%E5%90%8D%E8%8C%83%E5%9B%B4">...</a>*/private String sign;/*** 收单方式* CHECKOUT-返回收银台地址* PAY-直接支付*/private String acquirerType;/*** 交易金额,精确位数和币种有关,请查询附录交易币种*/private String amount;/*** 交易币种,ISO 4217 三位币种,具体支持币种⻅附录交易币种*/private String currency;/*** 商户网站交易流水号,每次请求的唯一标识,可用于后续订单查询和对账*/private String merchantTransactionId;/*** 商户自定义接收重定向的结果 URL;如 3DS验证,银行在线转账或虚拟钱包之类支付方式时,最后需要重定向到商户指定的⻚面地址*/private String shopperResultUrl;/*** 收银台页面取消支付操作时页面跳转地址,acquirerType=CHECKOUT,并且没有传送paymentBrand时候必传,条件必传*/private String shopperCancelUrl;/*** 异步通知地址,可选*/private String notificationUrl;/*** 商户扩展字段,可用于指定特定参数,会在响应体中原样返回(暂不可用),可选*/private String remark;/*** 建站平台标识,建站平台接入必传。作用是标识这笔交易的是从哪个建站平台发起的,条件必传*/private String merchantSource;/*** 用户ID,用户在HC的用户ID*/private String customerId;/*** 用户注册邮箱*/private String email;/*** 商品名称,例如钻石*/private String name;/*** 商品数量*/private String number;
}

沙箱环境获取收银台DTO类

import lombok.Data;
import java.io.Serializable;/*** PingPong支付获取收银台地址时返回值dto** @author Czw* @date 2023/05/06*/
@Data
public class PingPongPayCheckoutDto implements Serializable {private static final long serialVersionUID = 7575136778850419334L;/*** PingPong 商户店铺编号*/private String accId;/*** 交易金额*/private String amount;/*** PingPong 商户店商户号*/private String clientId;/*** 结果状态码*/private String code;/*** 交易币种,ISO 4217 三位币种,参考https://acquirer-api-docs-v3.pingpongx.com/pages/3c0bdf/*/private String currency;/*** 结果描述*/private String description;/*** 商户网站的的交易流水号*/private String merchantTransactionId;/*** 由商户自定义本次交易结果通知的地址,一旦填写该参数,PingPongCheckout 将通过 Post 方式异步推送交易结果到该地址*/private String notificationUrl;/*** 当前交易关联的 PingPong 交易流水号*/private String relateTransactionId;/*** 签名内容*/private String sign;/*** MD5、SHA256*/private String signType;/*** SUCCESS-成功* FAILED-失败* PROCESSING-进行中* PENDING-处理中* REVIEW-待审核*/private String status;/*** PingPong 交易流水号*/private String transactionId;/*** 交易发起时间,yyyyMMddHHmmss*/private String transactionTime;/*** 本次结账请求的唯一标示,用于初始化JS-SDK*/private String token;/*** 仅在收银台模式下返回,JS-SDK的加载地址*/private String innerJsUrl;/*** 仅在收银台模式下返回,PingPong 支付收银台地址*/private String paymentUrl;
}

参数加密PingPongSignUtil

package com.hc.app.user.server.util.pingpong;import com.google.common.collect.Maps;import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;import com.hc.app.user.model.request.CoinRechargePingPongRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;import java.security.MessageDigest;
import java.util.Map;
import java.util.TreeMap;/*** @description: PingPong支付签名工具类* @author: Czw* @create: 2023-05-05 12:02**/
@Slf4j
public class PingPongSignUtil {/*** 签名方法-sha256*/public static final String SIGN_TYPE_SHA256 = "SHA256";/*** 签名方法-md5*/public static final String SIGN_TYPE_MD5 = "MD5";/*** 测试环境异步通知支付状态地址*/public static final String NOTIFICATION_URL_TEST = "";/*** 取消支付时跳转的界面*/public static final String SHOPPER_CANCEL_URL_TEST = "http://52.221.156.209/pay/pay_success.html?orderId=orderNum&status=4";/*** 支付的网站发起地址*/public static final String MERCHANT_SOURCE_URL = "";//------------------沙箱参数信息开始------------------------public static final String SANDBOX_ACCID = "2018092714313010016291";/*** 生成签名时的盐*/public static final String SANDBOX_SALT = "F78BC96A55548B2319EE68E0";//------------------沙箱参数信息结束------------------------/*** 部分参数签名,参与签名的字段*/private static final String[] includeFields = {"accId", "amount", "clientId", "cardNum", "currency", "merchantTransactionId","requestId", "signType", "transactionId"};/*** 签名秘钥*/private String salt = null;public PingPongSignUtil(String salt) {this.salt = salt;}public static void main(String[] args) {TreeMap<String, Object> signMap = new TreeMap<>();signMap.put("clientId", "2018092714313010016");signMap.put("accId", "2018092714313010016291");signMap.put("order", "2018092J7Y1K4U313010016291");String sha256 = signature("SHA256", SANDBOX_SALT, signMap);System.out.println(sha256);}/*** 执行签名** @param signType 签名类型 SHA256/MD5* @param salt     盐* @param signMap  待签名串* @return {@link String }* @author Czw* @date 2023/05/05*/public static String signature(String signType, String salt, TreeMap<String, Object> signMap) {String signContent = getPartSignParams(signMap);System.out.println("signContent=" + signContent);if (StringUtils.equalsIgnoreCase("MD5", signType)) {return md5Sign(salt, signContent);} else if (StringUtils.equalsIgnoreCase("SHA256", signType)) {return sha256(signContent, salt);}return null;}/*** 转换为符号映射** @param request 请求* @param signMap 标志地图* @author Czw* @date 2023/05/08*/public static void convertToSignMap(CoinRechargePingPongRequest request, TreeMap<String, Object> signMap) {// 获取对象的所有字段Field[] fields = request.getClass().getDeclaredFields();for (Field field : fields) {// 设置字段可访问field.setAccessible(true);// 获取字段的值Object value = null;try {value = field.get(request);} catch (IllegalAccessException e) {throw new RuntimeException(e);}if (value != null) {// 将字段名和值作为键值对添加到TreeMap中signMap.put(field.getName(), value);}}}/*** 获取待签名串(部分字段签名)*/private static String getPartSignParams(TreeMap<String, Object> signMap) {//添加需要签名的字段TreeMap<String, Object> resultMap = Maps.newTreeMap();for (String param : includeFields) {String value = (String) signMap.get(param);if (StringUtils.isNotBlank(value)) {//应SDK要求对参数进行trim操作resultMap.put(param, value.trim());}}return getSignParams(resultMap);}/*** 获取待签名串*/private static String getSignParams(TreeMap<String, Object> resultMap) {StringBuilder stringBuilder = new StringBuilder();int paramNum = 0;for (Map.Entry<String, Object> signEntry : resultMap.entrySet()) {paramNum++;stringBuilder.append(signEntry.getKey());stringBuilder.append("=");stringBuilder.append(signEntry.getValue());if (paramNum < resultMap.size()) {stringBuilder.append("&");}}log.debug("content:【{}】", stringBuilder);return stringBuilder.toString();}private static String md5Sign(String salt, String content) {try {MessageDigest md = MessageDigest.getInstance("MD5");md.update(salt.getBytes());md.update(content.getBytes());byte[] digest = md.digest();return byteToHexString(digest);} catch (Exception e) {log.error("md5签名失败", e);}return null;}private static String sha256(String content, String salt) {try {if (StringUtils.isBlank(salt)) {throw new RuntimeException("salt is null");}String contentStr = salt.concat(content);return DigestUtils.sha256Hex(contentStr.getBytes(StandardCharsets.UTF_8)).toUpperCase();} catch (Exception e) {log.error("sha256", e);}return null;}public static String byteToHexString(byte[] b) {StringBuilder hexString = new StringBuilder();for (byte value : b) {String hex = Integer.toHexString(value & 0xFF);if (hex.length() == 1) {hex = '0' + hex;}hexString.append(hex.toUpperCase());}return hexString.toString();}
}

沙箱环境收银台代码

    //------------------沙箱参数信息开始------------------------public static final String SANDBOX_ACCID = "2018092714313010016291";/*** 生成签名时的盐*/public static final String SANDBOX_SALT = "F78BC96A55548B2319EE68E0";/*** 收单方式-直接支付*/public static final String ACQUIRER_TYPE_CHECKOUT = "CHECKOUT";@GetMapping("/pingPongPay")public String pingPongPay() {CoinRechargePingPongRequest pingPongRequest = new CoinRechargePingPongRequest();String uid = UUID.randomUUID().toString();pingPongRequest.setRequestId(uid);pingPongRequest.setAccId(SANDBOX_ACCID);pingPongRequest.setSignType(SIGN_TYPE_SHA256);pingPongRequest.setAcquirerType(ACQUIRER_TYPE_CHECKOUT);pingPongRequest.setAmount("1.99");pingPongRequest.setCurrency("USD");String merchantTransactionId = UUID.randomUUID().toString();pingPongRequest.setMerchantTransactionId(merchantTransactionId);//URL是自定义的支付结果显示页,pingpongpay接收到后会在后面拼接订单ID"?orderId=123"pingPongRequest.setShopperResultUrl("http://52.221.156.209/pay/pay_success.html");//取消支付的URLpingPongRequest.setShopperCancelUrl("http://52.221.156.209/pay/pay_success.html?orderId=orderNum&status=4".replace("orderNum", merchantTransactionId));//这个URL是我们自定义的回调订单回调接口地址,参考文档:https://acquirer-api-docs-v3.pingpongx.com/pages/d0ddb3/#%E4%B8%9A%E5%8A%A1%E7%B3%BB%E7%BB%9F%E4%BA%A4%E4%BA%92%E6%B5%81%E7%A8%8BpingPongRequest.setNotificationUrl("http://52.221.156.209/pay/callback");pingPongRequest.setRemark("");pingPongRequest.setMerchantSource("");pingPongRequest.setCustomerId("10028");pingPongRequest.setEmail("19**790****@163.com");pingPongRequest.setName("Token充值");pingPongRequest.setNumber("67499");TreeMap<String, Object> signMap = new TreeMap<>();// 将pingPongRequest对象字段放入map中convertToSignMap(pingPongRequest, signMap);pingPongRequest.setSign(PingPongSignUtil.signature("SHA256", SANDBOX_SALT, signMap));String url;if (envConfig.isProd()) {//生产url = "https://acquirer-payment.pingpongx.com/acquirer/payment";} else {//沙箱url = "https://sandbox-acquirer-payment.pingpongx.com/acquirer/payment";}// 设置请求头HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);// 构造请求实体HttpEntity<String> requestEntity = new HttpEntity<>(JSONObject.toJSONString(pingPongRequest), headers);//设置超时时间HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();factory.setConnectTimeout(5000);factory.setReadTimeout(5000);//请求pingpong支付ResponseEntity<JSONObject> result = restTemplate.postForEntity(url, requestEntity, JSONObject.class);System.out.println(result);JSONObject body = result.getBody();SysLogger.info(this.getClass(), "payermax 创建订单结果" + body.toJSONString());PingPongPayCheckoutDto payCheckoutDto = JsonFieldUtil.jsonToObj(body, PingPongPayCheckoutDto.class);System.out.println(payCheckoutDto);  return responseEntity.toString();}

需要说明的是,请求地址URL在sdk中是一个使用了占位符的地址:https://{host}/acquirer/payment,其中的{host}代表的是对接流程(必读)对应的资源,所以完整url是代码中的https://sandbox-acquirer-payment.pingpongx.com/acquirer/payment地址,包括salt以及accid都是该界面中的资源;我们在设置获取收银台URL时一定要设置NotificationUrl参数,它的作用时让pingpong支付支付的过程中以这样的频率 2s/5s/10s/30s/1m/10m/30m/1h/2h/1d/2d访问这个NotificationUrl,我们可以在这个接口中更新支付订单的状态以及对应状态下的业务,另外回调函数NotificationUrl的接口中为了保证安全需要做IP访问限制,只允许pingpongpay的服务器IP访问

官方服务器ip

沙箱的测试效果

apifox测试界面

可以看一下返回参数paymentUrl,它就是我们需要的支付界面,在浏览器中访问查看,与apifox中的paymentUrl地址一致

支付结果页

跨境电商支付平台-PingPong Pay(实现收银台模式沙箱支付)相关推荐

  1. 自建跨境电商系统平台,多语言跨境电子商务系统开发搭建,建站方案

    电子商务系统建站开发需做好方案与搭配,以下是方案的选择与搭建部署的建议 首先是什么是跨境电商?跨境电商是指企业利用互联网技术和电子商务平台,进行跨国贸易活动,实现不同国家之间的商品交易.跨境电商系统需 ...

  2. 跨境电商国内平台有哪些

    中国拥有全球最大的电子商务市场,并且其增长迅速.在繁荣的电子商务经济下,中国已经产生了许多优秀的电子商务平台.Ueeshop给大家看看跨境电商国内平台都有哪些.   1.淘宝网   在中国,淘宝网可以 ...

  3. 跨境电商各平台的入驻条件都有哪些?

    跨境电商各平台的入驻条件都有哪些? 近几年来,国内电商发展开始趋于平缓,而纵观跨境电商,却依然处于一路突飞猛进的趋势,由此,国内不少企业商家纷纷开始转变战略入局跨境电商,以此来获取更深层次的发展空间和 ...

  4. 跨境电商多平台运营库存管理难 选对erp软件很重要

    近几年跨境电商迅速发展,大多数跨境电商卖家都是多平台多渠道同时运营,这将不可避免地导致更多的管理困难,其中库存管理问题成为了跨境电商卖家的核心关注点之一,也是大部分跨境电商卖家头疼的问题. 库存管理是 ...

  5. 杨学海:跨境电商新通道-进口保税直邮模式解析

    为什么80%的码农都做不了架构师?>>>    杨学海:跨境电商新通道-进口保税直邮模式解析 广州威云供应链管理公司总经理杨学海在第九届中国中小企业电子商务大会上表示,其品牌海外通要 ...

  6. 跨境电商东南亚平台Shopee、Lazada到底怎么样?如何高效补单?

    作为 Lazada 在东南亚的最大竞争者,Shopee这两年表现尤为优异,特别是在马来.中国台湾.印尼市场.在 Shopee 这样没有完全成熟,中小卖家现在抓紧入驻还能占最后一席之地.Shopee 目 ...

  7. 跨境电商/外贸平台电子商务平台服务

    跨境电商(全称:跨境电子商务)这个名词从2011年开始出现,是指不同国境地域的交易主体之间,以电子商务的方式达成交易,在线订购.支付结算,并通过跨境物流递送商品,清关,最终送达,完成交易的一种国际商业 ...

  8. 分享个常用的跨境电商数据分析平台

    在跨境电商人眼中,适合用在跨境电商数据分析上的大数据分析平台该是怎样的?是效率高.财务指标计算快.业务能随时自助分析,最好是能将平台自身的分析经验分享给跨境电商企业,为企业提供更专业的服务.这样的大数 ...

  9. 胖姐分享:VOVA、Joom、wish哪个是值得做的跨境电商黑马平台

    VOVA跨境电商 VOVA是欧洲面向全球市场的一站式移动端跨境电商平台,运营模式与Wish.Joom类似,都是全类目,以fasion和home为主.支持20种主流语言和25种主流货币.目前Vova平台 ...

最新文章

  1. mysql锁表_MYSQL锁表问题的解决方法
  2. Project Chameleon Work In Progress 10
  3. python套接字编程_Python网络编程 Python套接字编程
  4. 【Ubuntu】ubuntu物理机安装方法:wubi
  5. IdentityServer4 之 Resource Owner Password Credentials 其实有点尴尬
  6. php常见的面试题目
  7. oracle批量联机,Oracle 12.2 使用联机重定义对表进行多处改变
  8. linux -对称加密、 非对称加密
  9. Python doc转docx
  10. Java之StringBuffer使用方法
  11. Jersey入门教程
  12. 数据库系统概念 第三章 习题答案
  13. harmonyos bate,HarmonyOS 生态最重的拼图,手机开发者 Beta 版终于到来
  14. 七牛删除视频文件操作
  15. java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11GraphicsEnvironment
  16. 机器学习天坑总结篇(TensorFlow)
  17. Hyperledger Fabric的网络拓扑图与交易流程
  18. 计算机教学学期小结,学年第一学期信息技术教学工作总结
  19. h5封装table表格 vue
  20. 20种赛博朋克摄影调色luts预设

热门文章

  1. Xcode: 运行卡顿处理
  2. stm32硬件JPEG编码过程分析
  3. LINUX-DNS部署 超详细
  4. vue使用优化后的动画级别的定时器 requestAnimationFrame 比setInterval, setTimeout效果要高很多
  5. 基于51单片机的蓝牙智能小车——《入门篇》
  6. 深入理解设计模式-简单工厂模式vs工厂方法模式vs抽象工厂模式对比讲解
  7. JS获取字符串中某字符出现n次的下标,递归解决
  8. 【综合评价方法 独立性权系数法】指标权重确定方法之独立性权系数法
  9. [CEOI2004] Sweets
  10. vant踩坑地址AddressList地址编辑篇