第三方支付
在线支付
在线支付是指卖方与买方通过因特网上的电子商务网站进行交易时,银行为其提供网上资金结算服务的一种业务。它为企业和个人提供了一个安全、快捷、方便的电子商务应用环境和网上资金结算工具。在线支付不仅帮助企业实现了销售款项的快速归集,缩短收款周期,同时也为个人网上银行客户提供了网上消费支付结算方式,使客户真正做到足不出户,网上购物。
聚合支付
聚合支付:也称“融合支付”,是指只从事“支付、结算、清算”服务之外的“支付服务”,依托银行、非银机构或清算组织,借助银行、非银机构或清算组织的支付通道与清结算能力,利用自身的技术与服务集成能力,将一个以上的银行、非银机构或清算组织的支付服务,整合到一起,为商户提供包括但不限于“支付通道服务”、“集合对账服务”、“技术对接服务”、“差错处理服务”、“金融服务引导”、“会员账户服务”、“作业流程软件服务”、“运行维护服务”、“终端提供与维护”等服务内容,以此减少商户接入、维护支付结算服务时面临的成本支出,提高商户支付结算系统运行效率的,并收取增值收益的支付服务。
第三方支付流程
回调方式:同步回调、异步回调
回调场景: 告诉商户支付通知结果
同步回调: 整个支付流程完毕,使用同步方式将参数重定向给商户平台,一般场景用于展示结果。
异步回调: 第三方支付接口发一个后台通知给商户平台,一般场景用户修改订单信息。
数据安全加密
单向加密
单向加密算法主要用来验证数据传输的过程中,是否被篡改过。
MD5(Message Digest algorithm 5,信息摘要算法)
SHA(Secure Hash Algorithm,安全散列算法)
HMAC(Hash Message Authentication Code,散列消息鉴别码
对称加密
你可以这么理解,一方通过密钥将信息加密后,把密文传给另一方,另一方通过这个相同的密钥将密文解密,转换成可以理解的明文。他们之间的关系如下:
明文 <-> 密钥 <-> 密文
常用对称加密方案 DES、AES、Base64
非对称加密
在通信双方,如果使用非对称加密,一般遵从这样的原则:公钥加密,私钥解密。同时,一般一个密钥加密,另一个密钥就可以解密。
因为公钥是公开的,如果用来解密,那么就很容易被不必要的人解密消息。因此,私钥也可以认为是个人身份的证明。
如果通信双方需要互发消息,那么应该建立两套非对称加密的机制(即两对公私钥密钥对),发消息的一方使用对方的公钥进行加密,接收消息的一方使用自己的私钥解密。
就从上面提到的这个对称加密的缺点开始,怎么做到即时一个人的密钥被盗窃了,最起码保证你给其他人发送密文不被破解。于是,人们就想出了这么个办法,首先,我们停止分享共同的密钥,因为上面的 bug 就是来源于共享一个密钥,那么怎么办呢?每个人生成一个“私钥-公钥”对,这个私钥需要每个人自行进行保护!公钥可以随便分享,后面详细说,同时,生成的这个“私钥-公钥”对还有个强大的功能就是,使用私钥加密的信息,只能由该私钥对应的公钥才能解密,使用公钥加密的信息,只能由该公钥对应的私钥才能解密!
常用非对称加密 RSA 一般用于数据安全加密特别,支付领域。
参数验签
外网映射工具
Ngrok使用
●windows用户:
1,下载windows版本的客户端,解压到你喜欢的目录
2,在命令行下进入到path/to/windows_386/下
3,执行 ngrok -config=ngrok.cfg -subdomain xxx 80 //(xxx 是你自定义的域名前缀)
4,如果开启成功 你就可以使用 xxx.tunnel.qydev.com 来访问你本机的 127.0.0.1:80 的服务啦
5,如果你自己有顶级域名,想通过自己的域名来访问本机的项目,那么先将自己的顶级域名解析到123.57.165.240(域名需要已备案哦),然后执行./ngrok -config=ngrok.cfg -hostname xxx.xxx.xxx 80 //(xxx.xxx.xxx是你自定义的顶级域名)
6,如果开启成功 你就可以使用你的顶级域名来访问你本机的 127.0.0.1:80 的服务啦
7,如果失败 就加下交流群 反馈下问题 本屌会看看什么原因....吧
Natapp使用
windows ,点击开始->运行->命令行提示符 后进入 natapp.exe的目录
运行 natapp -authtoken= 175396706488ac93
支付宝开发环境
支付宝沙箱
蚂蚁沙箱环境(Beta)是协助开发者进行接口功能开发及主要功能联调的辅助环境。沙箱环境模拟了开放平台部分产品的主要功能和主要逻辑(当前沙箱支持产品请参考“沙箱支持产品列表”)。
在开发者应用上线审核前,开发者可以根据自身需求,先在沙箱环境中了解、组合和调试各种开放接口,进行开发调通工作,从而帮助开发者在应用上线审核完成后,能更快速、更顺利的进行线上调试和验收工作。
如何使用和配置沙箱环境请参考《沙箱环境使用说明》。
注意:
- 由于沙箱为模拟环境,在沙箱完成接口开发及主要功能调试后,请务必在蚂蚁正式环境进行完整的功能验收测试。所有返回码及业务逻辑以正式环境为准。
- 为保证沙箱稳定,沙箱环境测试数据会进行定期数据清理。Beta测试阶段每周日中午12点至每周一中午12点为维护时间。在此时间内沙箱环境部分功能可能会不可用,敬请谅解。
- 请勿在沙箱进行压力测试,以免触发相应的限流措施,导致无法正常使用沙箱环境。
- 沙箱支持的各个开放产品,沙箱使用的特别说明请参考各产品的快速接入文档或技术接入文档章节。
环境准备
修改AlipayConfig信息
/* *
*类名:AlipayConfig
*功能:基础配置类
*详细:设置帐户有关信息及返回路径
*说明:
*以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。
*该代码仅供学习和研究支付宝接口使用,只是提供一个参考。
*/
public class AlipayConfig {
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "2016092100564758";
// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCuXtPexot6F4hVXEivfpCL/uiV38yvPc2RqOiQ2AeZFmBuc+CMT4oytiegKGqxEURYYmKFA/K6/n3BW4FMMx3jEW+n/gQC6uUxD2LWDmorJC14xv2B34UjdRXwneNh5GKD5ZrH6AH4bh2urNscxV9pOZlfdcqa99uT00oUjSlk3B3XYxJoqbxkuXwCkUv8IGrgAmM5pLOsoJF87fOzQ1l8UqGWT5GwuJmJZBXM5UBUvjKzp+H7BmIf0GU9dh6o4LpQdiX8wfDWGfqKhiLPHqa66+U7b+lsDh2pu+daYYhhqyjTAyL3epOhmMhEG7zCcT0PQj4WPtaYRDMVD6E6IKBzAgMBAAECggEAfw5l/6xYsYw4MUrfQ9FLblc+DwdWVFMKWZrka7aeQrSFa7ZP5q2Gm9ETKqaIp6FXVbfK5fWshwkthRkyK94LZwurepOjRKT4gDkf4a37OphP8fO9gUbn8qA8bmn957TM/CLwF748wVMrmb4mot5G2Zu44FAqY6U7gImzLyp1ASITdlDbl1FU0Zukgsk78/rRipMVqeeMvHCwZdJPzrDX0MhNVcivBgJF7u2d49Ypm7PvvCn2OHLtiiKvyLbqkK7r3cR5Si/0aaMsFwvrPbd0a+QJpYDtU/ry+kw8izqcBTC1u4inPl8Fa9F/1qjAYUskCOyOXIfOoKbBzku5nO+VMQKBgQD3YIj+2fle1t8eCk87OVdltNdXwoduld+Xee4WiV4W2WBfHDOk81usc8R/wGl5p6AJpoVT4wVaaDYWqjppAd/RweBlfG9wGH5ytXWrys9AcViC4bIRnQmI6+pHFFMSaRDfVYHdxqzQXUgL0+2GPCqxY1aDJJFAVcLL1CjDrfXLdQKBgQC0ctGeFyYRDOQUiP2SY7Drqke+yisN79vfZd7SZ41UYf6AzSBesi8iaEdag7qhEMcVGQxetnadOXN2hC/jMy5CBnCpBHLjwcfNNe8egEqKu9KiKwZAmMwVbSSV3adon6818YWASxz5pHN/VbolW+VFoH0IjU/n70FkkUYH7TEHRwKBgF394u+aWKLNV6ctWZ9yESAGz098DUNaVMNUQ79yYDqkS3a323OQN8PVlNLJhAoCQ8+G4t/VwWHxeKOx+FGPscAcPyuwVRMta1YgVl54x7h/mJbaNHN2zHmm0bRCJ7I2E4AYGCjw5Raias57rqMzVzFhQizAByR/sW0K5pY7EcpFAoGANZVm46ASILwIOoTXb5IE5mZBOcmE8XWJgBQbD7XKRQV4crz24MfesUPv9FPrpop546z1fGaIYHW/8LCeG8SF9vs8lyQIDdPsRea/I/qKqBnQGXHXQHVfHPm1BH+2h53rhIQ81XT4nLUVyvkk9pUMRxm6J0D3OnNUos1000O+7F8CgYBpQB1YGhAw+c0ludeFmvyglHEL+dQxfnGCKWe3DseURG0gVCH8Ty7o+eB5CrbGgZobw6GJG0YcNBv1GqkloodJ3Rgdiikvpu7UJ005LOZxOjmqIvKiYgFJd9Fe2kLkoOpcfrdpc8pQrTDEKtGX8hHvP1/2zfEQAo6Mcnz8layS8g==";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
public static String alipay_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDIgHnOn7LLILlKETd6BFRJ0GqgS2Y3mn1wMQmyh9zEyWlz5p1zrahRahbXAfCfSqshSNfqOmAQzSHRVjCqjsAw1jyqrXaPdKBmr90DIpIxmIyKXv4GGAkPyJ/6FTFY99uhpiq0qadD/uSzQsefWo0aTvP/65zi3eof7TcZ32oWpwIDAQAB";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "http://itmayiedu.tunnel.qydev.com/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url = "http://itmayiedu.tunnel.qydev.com/return_url.jsp";
// 签名方式
public static String sign_type = "RSA";
// 字符编码格式
public static String charset = "utf-8";
// 支付宝网关
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
// 支付宝网关
public static String log_path = "C:\\";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
* @param sWord 要写入日志里的文本内容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
运行Demo项目
项目使用集成支付宝
支付数据库表结构
CREATE TABLE `payment_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`userid` int(11) DEFAULT NULL,
`typeid` int(2) DEFAULT NULL,
`orderid` varchar(50) DEFAULT NULL,
`price` decimal(10,0) DEFAULT NULL,
`source` varchar(10) DEFAULT NULL,
`state` int(2) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`updated` datetime DEFAULT NULL,
`platformorderid` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;
创建newbies-shopp-pay服务工程
application配置文件
server:
port: 8768
# context-path: /member
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: pay
redis:
host: 47.100.50.60
password: 123
port: 6379
pool:
max-idle: 100
min-idle: 1
max-active: 1000
max-wait: -1
#数据库连接信息
datasource:
name: test
url: jdbc:mysql://127.0.0.1:3306/newbies-pay
username: root
password: root
# 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
filters: stat
maxActive: 20
initialSize: 1
maxWait: 60000
minIdle: 1
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxOpenPreparedStatements: 20
Maven依赖
<dependencies>
<dependency>
<groupId>com.newbies</groupId>
<artifactId>newbies-shopp-api-pay</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- springboot整合mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.1991wangliang/alipay-sdk -->
<dependency>
<groupId>com.github.1991wangliang</groupId>
<artifactId>alipay-sdk</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
数据库访问层
@Mapper
public interface PaymentInfoDao {
@Select("select * from payment_info where id=#{id}")
public PaymentInfo getPaymentInfo(@Param("id") Long id);
@Insert("insert into payment_info ( id,userid,typeid,orderid,platformorderid,price,source,state,created,updated) value(null,#{userId},#{typeId},#{orderId},#{platformorderId},#{price},#{source},#{state},#{created},#{updated})")
@Options(useGeneratedKeys = true, keyProperty = "id") // 添加该行,product中的id将被自动添加
public Integer savePaymentType(PaymentInfo paymentInfo);
@Select("select * from payment_info where orderId=#{orderId}")
public PaymentInfo getByOrderIdPayInfo(@Param("orderId") String orderId);
@Update("update payment_info set state =#{state},payMessage=#{payMessage},platformorderId=#{platformorderId},updated=#{updated} where orderId=#{orderId} ")
public void updatePayInfo(PaymentInfo paymentInfo);
}
业务逻辑层
@Service
public class AliBaBaManagerImpl implements PayManager {
@Override
public String payInfo(PaymentInfo paymentInfo) throws AlipayApiException {
//获得初始化的AlipayClient
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
alipayRequest.setNotifyUrl(AlipayConfig.notify_url);
//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no =paymentInfo.getOrderId();
//付款金额,必填
String total_amount = paymentInfo.getPrice()+"";
//订单名称,必填
String subject ="蚂蚁课堂会员费用";
// //商品描述,可空
// String body = new String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//若想给BizContent增加其他可选请求参数,以增加自定义超时时间参数timeout_express来举例说明
//alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
// + "\"total_amount\":\""+ total_amount +"\","
// + "\"subject\":\""+ subject +"\","
// + "\"body\":\""+ body +"\","
// + "\"timeout_express\":\"10m\","
// + "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求参数可查阅【电脑网站支付的API文档-alipay.trade.page.pay-请求参数】章节
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
return result;
}
}
服务实现层
@RestController
public class PayServiceImpl extends BaseApiService implements PayService {
@Autowired
private PaymentInfoDao paymentInfoDao;
@Autowired
private AliBaBaManagerImpl aliBaBaManagerImpl;
@Override
public ResponseBase getPayToken(@RequestBody PaymentInfo paymentInfo) {
// 1.插入参数提交信息
Integer savePaymentType = paymentInfoDao.savePaymentType(paymentInfo);
if (savePaymentType <= 0) {
return setResultError("系统错误!");
}
// 2.生成对应token
String payToken = TokenUtils.getPayToken();
// 3.存放在redis中
Integer payId = paymentInfo.getId();
baseRedisService.setString(payToken, payId + "", Constants.PAY_TOKEN_MEMBER_TIME);
// 4.返回token給消費者
JSONObject result = new JSONObject();
result.put("payToken", payToken);
return setResultSuccess(result);
}
@Override
public ResponseBase payInfo(String payToken) {
if (StringUtils.isEmpty(payToken)) {
return setResultError("token不能为空!");
}
String payId = (String) baseRedisService.getString(payToken);
if (StringUtils.isEmpty(payId)) {
return setResultError("支付已经超时!");
}
PaymentInfo paymentInfo = paymentInfoDao.getPaymentInfo(Long.parseLong(payId));
if (paymentInfo == null) {
return setResultError("未找到交易类型.");
}
// 判断类型 调用 具体业务接口
Long typeId = paymentInfo.getTypeId();
PayManager payManager = null;
// 调用支付接口
if (typeId == 1) {
payManager = aliBaBaManagerImpl;
}
try {
String payInfo = payManager.payInfo(paymentInfo);
JSONObject payInfoJSON = new JSONObject();
payInfoJSON.put("payInfo", payInfo);
return setResultSuccess(payInfoJSON);
} catch (AlipayApiException e) {
return setResultError("支付错误!");
}
}
}
项目启动
@SpringBootApplication
@EnableEurekaClient
public class PayApp {
public static void main(String[] args) {
SpringApplication.run(PayApp.class, args);
}
}
创建newbies-shopp-pc-web
@Controller
public class PayController {
@Autowired
private PayServiceFegin payServiceFegin;
@RequestMapping("/pay")
public void pay(String payToken, HttpServletResponse resp) throws IOException {
resp.setContentType("text/html;charset=utf-8");
ResponseBase payInfo = payServiceFegin.payInfo(payToken);
PrintWriter out = resp.getWriter();
if (!payInfo.getRtnCode().equals(Constants.HTTP_RES_CODE_200)) {
out.println("ERROR");
return;
}
LinkedHashMap mapPayInfo = (LinkedHashMap) payInfo.getData();
String payInfoHtml = (String) mapPayInfo.get("payInfoHtml");
out.println(payInfoHtml);
out.close();
}
}
支付回调
PayCallBackService
@RequestMapping("/callBack")
public interface PayCallBackService {
// // 同步回调
@RequestMapping("/synCallBackService")
public ResponseBase synCallBack(@RequestParam Map<String, String> params);
// // 异步回调
@RequestMapping("/asynCallBackService")
public String asynCallBack(@RequestParam Map<String, String> params );
}
PayCallBackServiceImpl
@Slf4j
@RestController
public class PayCallBackServiceImpl extends BaseApiService implements PayCallBackService {
@Autowired
private PaymentInfoDao paymentInfoDao;
// 同步回调
public ResponseBase synCallBack(@RequestParam Map<String, String> params) {
// 获取支付宝GET过来反馈信息
try {
log.info("####同步回调开始####params:",params);
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type); // 调用SDK验证签名
// ——请在这里编写您的程序(以下代码仅作参考)——
if (!signVerified) {
return setResultError("验签失败!");
}
// 商户订单号
String out_trade_no = params.get("out_trade_no");
// 支付宝交易号
String trade_no = params.get("trade_no");
// 付款金额
String total_amount = params.get("total_amount");
JSONObject data = new JSONObject();
data.put("out_trade_no", out_trade_no);
data.put("trade_no", trade_no);
data.put("total_amount", total_amount);
return setResultSuccess(data);
} catch (Exception e) {
log.info("######PayCallBackServiceImpl##ERROR:#####", e);
return setResultError("系统错误!");
}finally{
log.info("####同步回调结束####params:",params);
}
}
// 异步回调
public String asynCallBack(@RequestParam Map<String, String> params) {
// 获取支付宝GET过来反馈信息
try {
log.info("####异步回调开始####params:",params);
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type); // 调用SDK验证签名
// ——请在这里编写您的程序(以下代码仅作参考)——
if (!signVerified) {
return "fail";
}
// 商户订单号
String outTradeNo = params.get("out_trade_no");
PaymentInfo paymentInfo = paymentInfoDao.getByOrderIdPayInfo(outTradeNo);
if(paymentInfo==null){
return "fail";
}
Integer state = paymentInfo.getState();
if (state.equals("1")) {
return "success";
}
// 支付宝交易号
String trade_no = params.get("trade_no");
// 交易状态
String trade_status = params.get("trade_status");
if (trade_status.equals("TRADE_SUCCESS")) {
paymentInfo.setPayMessage(params.toString());
paymentInfo.setPlatformorderId(trade_no);
paymentInfo.setState(1);
paymentInfoDao.updatePayInfo(paymentInfo);
}
return "success";
} catch (Exception e) {
log.info("######PayCallBackServiceImpl##ERROR:#####", e);
return "fail";
}finally{
log.info("####异步回调结束####params:",params);
}
}
}
PayController
// 同步回调
@RequestMapping("/callBack/synCallBack")
public void synCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setContentType("text/html;charset=utf-8");
Map<String, String[]> requestParams = request.getParameterMap();
Map<String, String> params = new HashMap<String, String>();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
PrintWriter writer = response.getWriter();
ResponseBase synCallBack = payCallBackFegin.synCallBack(params);
if (!synCallBack.getRtnCode().equals(Constants.HTTP_RES_CODE_200)) {
return;
}
LinkedHashMap data = (LinkedHashMap) synCallBack.getData();
String htmlFrom = "<form name='punchout_form'"
+ " method='post' action='http://127.0.0.1/callBack/synSuccessPage' >"
+ "<input type='hidden' name='outTradeNo' value='" + data.get("out_trade_no") + "'>"
+ "<input type='hidden' name='tradeNo' value='" + data.get("trade_no") + "'>"
+ "<input type='hidden' name='totalAmount' value='" + data.get("total_amount") + "'>"
+ "<input type='submit' value='立即支付' style='display:none'>"
+ "</form><script>document.forms[0].submit();" + "</script>";
writer.println(htmlFrom);
writer.close();
}
// 同步回调,解决隐藏参数
@RequestMapping(value = "/callBack/synSuccessPage", method = RequestMethod.POST)
public String synSuccessPage(HttpServletRequest request, String outTradeNo, String tradeNo, String totalAmount) {
request.setAttribute("outTradeNo", outTradeNo);
request.setAttribute("tradeNo", tradeNo);
request.setAttribute("totalAmount", totalAmount);
return PAY_SUCCESS;
}
// 异步回调
@ResponseBody
@RequestMapping("/callBack/asynCallBack")
public String asynCallBack(HttpServletRequest request, HttpServletResponse response) throws IOException {
Map<String, String[]> requestParams = request.getParameterMap();
Map<String, String> params = new HashMap<String, String>();
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
}
// 乱码解决,这段代码在出现乱码时使用
//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
params.put(name, valueStr);
}
return payCallBackFegin.asynCallBack(params);
}
订单服务
数据库表
CREATE TABLE `order_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`orderNumber` varchar(255) DEFAULT NULL COMMENT '订单编号',
`isPay` int(50) DEFAULT NULL COMMENT '0 未支付,1已支付',
`payId` varchar(100) DEFAULT NULL,
`userId` int(50) DEFAULT NULL,
`created` datetime NOT NULL,
`updated` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
OrderServiceImpl
@RestController
public class OrderServiceImpl extends BaseApiService implements OrderService {
@Autowired
private OrderDao orderDao;
@Override
public ResponseBase updateOrderId(@RequestParam("isPay") Long isPay, @RequestParam("payId") String aliPayId,
@RequestParam("orderNumber") String orderNumber) {
int updateOrder = orderDao.updateOrder(isPay, aliPayId, orderNumber);
if (updateOrder <= 0) {
return setResultError("系统错误!");
}
return setResultSuccess();
}
}
回调代码
@Override
public String asynCallBack(@RequestParam Map<String, String> params) {
// 1.日志记录
log.info("#####支付宝异步通知synCallBack#####开始,params:{}", params);
// 2.验签操作
try {
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key,
AlipayConfig.charset, AlipayConfig.sign_type); // 调用SDK验证签名
log.info("#####支付宝异步通知signVerified:{}######", signVerified);
// ——请在这里编写您的程序(以下代码仅作参考)——
if (!signVerified) {
return Constants.PAY_FAIL;
}
// 商户订单号
String outTradeNo = params.get("out_trade_no");
// 调用订单数据库修改订单表数据库
// 3 处理幂等
PaymentInfo orderIdPayInfo = paymentInfoDao.getByOrderIdPayInfo(outTradeNo);
if (orderIdPayInfo == null) {
return Constants.PAY_FAIL;
}
// 判断是否已经支付过,如果已经支付过,返回success,防止重试产生重复
Integer state = orderIdPayInfo.getState();
if (state == 1) {
return Constants.PAY_SUCCESS;
}
// 支付宝交易号
String tradeNo = params.get("trade_no");
orderIdPayInfo.setState(1);
orderIdPayInfo.setPlatformorderId(tradeNo);
orderIdPayInfo.setPayMessage(params.toString());
Integer updatePayInfo = paymentInfoDao.updatePayInfo(orderIdPayInfo);
if (updatePayInfo <= 0) {
return Constants.PAY_FAIL;
}
// 调用订单接口通知修改订单状态
ResponseBase ordeResponseBase = orderFegin.updateOrderId(1l, tradeNo, outTradeNo);
if (!ordeResponseBase.getRtnCode().equals(Constants.HTTP_RES_CODE_200)) {
return Constants.PAY_FAIL;
}
return Constants.PAY_SUCCESS;
} catch (Exception e) {
log.error("####支付宝异步通知出现异常,ERROR:", e);
return Constants.PAY_FAIL;
} finally {
log.info("#####支付宝异步通知synCallBack#####结束,params:{}", params);
}
}
参考文献
常见支付问题
整个支付流程,怎么保证安全性
支付回调在网络延迟情况下,用户重复支付了怎么解决
支付回调在网络延迟情况下,幂等如何解决
分布式事物
事物特性
原子性(A)
所谓的原子性就是说,在整个事务中的所有操作,要么全部完成,要么全部不做,没有中间状态。对于事务在执行中发生错误,所有的操作都会被回滚,整个事务就像从没被执行过一样。
一致性(C)
事务的执行必须保证系统的一致性,就拿转账为例,A有500元,B有300元,如果在一个事务里A成功转给B50元,那么不管并发多少,不管发生什么,只要事务执行成功了,那么最后A账户一定是450元,B账户一定是350元。
隔离性(I)
所谓的隔离性就是说,事务与事务之间不会互相影响,一个事务的中间状态不会被其他事务感知。
持久性(D)
所谓的持久性,就是说一单事务完成了,那么事务对数据所做的变更就完全保存在了数据库中,即使发生停电,系统宕机也是如此。
分布式事物
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。以上是百度百科的解释,简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
分布式理论
CPA理论
一致性
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。分布式的一致性
对于一致性,可以分为从客户端和服务端两个不同的视角。从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,
以保证数据最终一致。一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。
如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性。
可用性
可用性指“Reads and writes always succeed”,即服务一直可用,而且是正常响应时间。
对于一个可用性的分布式系统,每一个非故障的节点必须对每一个请求作出响应。也就是,该系统使用的任何算法必须最终终止。当同时要求分区容忍性时,这是一个很强的定义:即使是严重的网络错误,
每个请求必须终止。
好的可用性主要是指系统能够很好的为用户服务,不出现用户操作失败或者访问超时等用户体验不好的情况。可用性通常情况下可用性和分布式数据冗余,负载均衡等有着很大的关联。
分区容错
分区容错性指“the system continues to operate despite arbitrary message loss or failure of part of the system”,即分布式系统在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性
和可用性的服务。
分区容错性和扩展性紧密相关。在分布式应用中,可能因为一些分布式的原因导致系统无法正常运转。好的分区容错性要求能够使应用虽然是一个分布式系统,而看上去却好像是在一个可以运转正常的整体。比如
现在的分布式系统中有某一个或者几个机器宕掉了,其他剩下的机器还能够正常运转满足系统需求,或者是机器之间有网络异常,将分布式系统分隔未独立的几个部分,各个部分还能维持分布式系统的运作,这样
就具有好的分区容错性。