study hard and make progress every day

This article has included to my lot DayDayUP:github.com/RobodLee/DayDayUP warehouse, welcome Star

  • Changbuy Mall (I) : Environment construction
  • Changgou Mall (2) : FastDFS distributed file system
  • Changbuy Mall (III) : Commodity management
  • Changgou Mall (4) : Lua, OpenResty and Canal realize advertising cache and synchronization
  • Elasticsearch realizes commodity search
  • Chang Buy mall (vi) : commodity search
  • Chang buy mall (seven) : Thymeleaf static page implementation
  • Changgou Mall (8) : Micro-service gateway and JWT token
  • Spring Security Oauth2
  • Changgou Mall (10) : Shopping cart
  • Changgou Mall (11) : Order
  • Changbuy Mall (12) : Access wechat Pay
  • Chang Buy mall (thirteen) : second kill system “on”
  • Chang Buy mall (14) : second kill system “under”

Payment process

In order to realize the function of payment, select wechat Pay here. The process is that we place an order through the order system, and then the order system calls the payment system to send a request to the wechat Pay server, and then get the QR code and return it to the user, and then the order system starts listening to MQ. After the user scans the payment code, the payment system stores the payment status into MQ. The order system detects that the user has already paid, sets the order to paid, and stores it in MySQL. The order system may fail to obtain the payment status due to network problems, so the order system will regularly send requests to the wechat payment server to query the order status.

Introduction to wechat Pay

In order to access wechat Pay, you need a verified service number, which I don’t have, so I can’t apply. Use the account provided by Dark Horse. I tried it. It works.

Appid (public account ID) : wX8397F8696b538317
McH_id (merchant number) : 1473426802
The key (merchant key) : T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
Copy the code

WeChat pay development documentation: pay.weixin.qq.com/wiki/doc/ap…

This official development document introduces wechat pay related apis and provides an SDK.

The SDK doesn’t have a lot of content, just a few classes, which I’ll cover briefly:

  • WXPayConfig: this is an abstract class, there are several methods, is used to obtain the core parameters, such as public id, merchant number, key, etc., so before using the class to instantiate several important parameters to configure.

  • WXPay: Order-related methods are encapsulated in this class, such as placing, querying, canceling orders, etc. There is a method called fillRequestData(), which is called every time an order is executed encapsulating the core parameters of WXPayConfig into a Map set of request parameters.

  • WXPayRequest: This is the request server. WXPay calls the method in this class to request the server. When executing the method, WXPay converts the Map set sent by WXPay into an XML string and sends the request to the server using HttpClient. Yes, wechat Pay uses XML to transmit data.

  • WXPayUtil: This is a utility class that encapsulates some common methods, such as Map to XML, XML to Map, etc.

There are two modes of Wechat Native payment, namely scanning code payment, used in this project. We used Mode 2.

The order will be generated in the order system of Changgou, and then some necessary parameters will be passed to the background of wechat Pay, and then a pre-paid order will be generated. The payment link will be returned to us, and we will generate the TWO-DIMENSIONAL code according to the payment link and send it to the user. Users scan the code to pay and then transfer the payment result to our background, so that the whole payment process is over.

The preparatory work

After the introduction of wechat payment, let’s talk about how to integrate wechat payment in the project. The video uses a third party dependency, I use the official one. The SDK for wechat Pay is not available in Maven’s remote repository, so you need to download it and import it manually. WXPayConfig is an abstract class that must be inherited to be instantiated. However, none of these abstract methods have permission modifiers, so the default is package access. Since Maven relies on this SDK, we will not write code in the same package as it, so we need to add the public modifier in front of these abstract methods. In addition, the SDK documentation provided by wechat also includes the implements abstract class. How could wechat make such a mistake

Now you can add the SDK to our local Maven repository by running the MVN install command in the root directory of the decompressed SDK.

When BUILD SUCCESS appears, the local Maven repository has been successfully added. This is where the second pitfall comes in, which could lead to Maven dependency conflicts if added to our project:

It can be seen that the package in conflict is SLf4J-simple, and the wechat Pay SDK happens to rely on this package, so when importing wechat Pay, you can just exclude this dependency.

<dependency>
    <groupId>com.github.wxpay</groupId>
    <artifactId>wxpay-sdk</artifactId>
    <version>3.0.9</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
        </exclusion>
    </exclusions>
</dependency>
Copy the code

Then you can create the payment project. Create a Moudle named changgou-service-wechatpay under changgou-service-wechatpay and add the dependency of wechatpay to the project.

server:
  port: 18090
spring:
  application:
    name: wechatpay
  main:
    allow-bean-definition-overriding: true
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:7001/eureka
  instance:
    prefer-ip-address: true
feign:
  hystrix:
    enabled: true
# hystrix configuration
hystrix:
  command:
    default:
      execution:
        timeout:
          # If enabled is set to false, the ribbon controls the request timeout
          enabled: true
        isolation:
          strategy: SEMAPHORE

# Wechat payment information configuration
wechat:
  Application id #
  app_id: wx8397f8696b538317
  # Merchant id
  mch_id: 1473426802
  # key
  key: T6m9iK73b0kn9g5v426MKfHQH7X8rKwb
  Pay the callback address
  notify_url: http://www.itcast.cn
Copy the code

The appID, McH_id, key, and notify_URL parameters can be read directly when used. So the payment microservice is set up.

Wechat Pay QR code generation

Add a Controller layer class WeChatPayController and create an entry method. Since qr code payment is called Native payment, we call it createNative. Accept an Order parameter. However, since the Order is created, it is much easier to use Order as a parameter.

// Create a qr code
@RequestMapping(value = "/create/native")
public Result createNative(@RequestBody Order order){
    Map<String,String> resultMap = weChatPayService.createNative(order);
    return new Result(true, StatusCode.OK,"Create qr code prepaid order success!",resultMap);
}
Copy the code

The remote interface of wechat payment is called by HttpClient. Since wechat has provided SDK, why do we need to duplicate the wheel? So INSTEAD of using wechat SDK as described in the video, I directly use HttpClient.

WXPayConfig needs to be implemented first.

public class MyWXPayConfig extends WXPayConfig {

    private String appId;

    private String mchId;

    private String key;

    public MyWXPayConfig(String appId, String mchId, String key) {
        this.appId = appId;
        this.mchId = mchId;
        this.key = key;
    }

    @Override
    public String getAppID(a) {
        return appId;
    }

    @Override
    public String getMchID(a) {
        return mchId;
    }

    @Override
    public String getKey(a) {
        return key;
    }

    @Override
    public InputStream getCertStream(a) {
        return null;
    }

    @Override
    public int getHttpConnectTimeoutMs(a) {
        return 8000;
    }

    @Override
    public int getHttpReadTimeoutMs(a) {
        return 10000;
    }

    @Override
    public IWXPayDomain getWXPayDomain(a) {
        IWXPayDomain iwxPayDomain = new IWXPayDomain() {
            @Override
            public void report(String domain, long elapsedTimeMillis, Exception ex) {}@Override
            public DomainInfo getDomain(WXPayConfig config) {
                return new IWXPayDomain.DomainInfo(WXPayConstants.DOMAIN_API, true); }};returniwxPayDomain; }}Copy the code

Then write the corresponding code in WeChatPayServiceImpl.

@Value("${wechat.appid}")
private String appId;

@Value("${wechat.mch_id}")
private String mcnId;

@Value("${wechat.key}")
private String key;

@Value("${wechat.notify_url}")
private String notifyUrl;

@Override
public Map<String, String> createNative(Order order) {
    try {
        Map<String, String> map = new HashMap<>(16);
        map.put("body"."Tencent Recharge Center -QQ Member Recharge");    // Product description
        map.put("out_trade_no", order.getId());      // Merchant order number
        map.put("total_fee", String.valueOf((int)(order.getTotalMoney() * 100))); // The marked price, in minutes
        map.put("spbill_create_ip"."127.0.0.1");    / / terminal IP
        map.put("trade_type"."NATIVE ");    // Transaction type, jsapI-JsAPI payment, native-native payment, app-app payment

        Map<String, String> response = wxpay.unifiedOrder(map);
        if (response == null || response.size() == 0) {
        	throw new RuntimeException("Order failed");
        }
        return response;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

The ** @value annotation is used to inject several parameters from the configuration file. The name of the key is explained in the document and cannot be changed. The unit of the amount is minutes. The amount we sent is yuan, so we need to multiply it by 100. Create an instance of MyWXPayConfig, pass in three parameters, create an object of WXPay, pass in config and notifyUrl, and call unifiedOrder()** to pass in map. In fact, we put the appID parameters in the map we passed in, so it is OK to put them in the map.

We have successfully created the order and got the ADDRESS of the QR code. Next, we just need to convert Code_URL into the picture of the QR code.

For testing, just find a QR code generator online and copy the Code_URL. In the project it is generated using Qrious.

<html>
<head>
<title>Two-dimensional code introduction small demo</title>
<! Create an IMG tag to store the image that displays the QR code 3. 4. Set the configuration item of the js object -->
<script src="qrious.js"> </script>	<! Qrious.js --> qrious.js
</head>
<body>
<img id="myqrious" >
</body>
<script>
   var qrious = new QRious({
   		 element:document.getElementById("myqrious"),// Specify the DOM object in which the image is located
   		 size:250.// Specify the pixel size of the image
		 level:'H'.// Specify the fault tolerance level of the QR code (H: 30% of the data can be recovered)
		 value:'weixin://wxpay/bizpayurl? pr=xKlU7lD'// Specify the real value that the qr code image represents
   })
</script>
</html>
Copy the code

Query order status

Sometimes the payment status may not be returned to our server in time due to network reasons, this time it is necessary to manually query, the code is very simple, I will not say more.

//WeChatPayController
@GetMapping(value = "/status/query")
public Result<Map<String,String>> queryPayStatus(@RequestParam String outTradeNo) {
    Map<String,String> resultMap = weChatPayService.queryPayStatus(outTradeNo);
    return new Result<>(true, StatusCode.OK,"Order query successful",resultMap); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -//WeChatPayServiceImpl
@Override
public Map<String, String> queryPayStatus(String outTradeNo) {
    try {
        Map<String, String> map = new HashMap<>();
        map.put("out_trade_no", outTradeNo);      // Merchant order number
        MyWXPayConfig config = new MyWXPayConfig(appId,mcnId, key);
        WXPay wxpay = new WXPay(config,notifyUrl);
        Map<String, String> response = wxpay.orderQuery(map);
        if (response == null || response.size() == 0) {
            throw new RuntimeException("Order query failed");
        }
        return response;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}
Copy the code

Test it out:

Payment result callback notification and RabbitMQ listening

In order to realize payment result callback notification, we must first tell the wechat Pay server the address of our payment result callback notification. Because we do not have public IP, the wechat server cannot actively send messages to our server, so we can use Intranet penetration. What I chose is an Intranet penetration plug-in of uTools, which can be used directly without registering an account, which is very convenient.

Then configure the Intranet penetration address to the place where notify_URL was configured.

Pay the callback address
notify_url: http://robod123.cn1.utools.club/wechat/pay/notify/url
Copy the code

The next step is to install RabbitMQ on the vm:

Docker pull docker. IO/rabbitmq: 3.7 - managementDownload the rabbitMQ image
docker run --name rabbitmq -d -p 15672:15672 -p 5672:5672 4b23cfb64730	# Install rabbitMQ. The last id is found by Docker images
docker exec -it rabbitmq /bin/bash						Enter rabbitMQ
rabbitmqctl add_user root 123456						Add user root with password 123456
rabbitmqctl set_permissions -p / root ". *" ". *" ". *"	# grant all permissions to user root
rabbitmqctl set_user_tags root administrator			# Assign the administrator role to the root user
Copy the code

Since both the payment microservice and the order microservice use RabbitMQ, add mq dependencies to both microservices

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
Copy the code

Then add rabbitMQ configuration information to the two microservices:

spring:			Configure the IP and port for RabbitMQ
  rabbitmq:
    host: 192.16831.200.
    port: 5672
    username: root
    password: 123456
Copy the code

Exchange and queue were created automatically by the program in the video, but I encountered a problem, that is, an error was reported when the order micro service was started, so the service could not start:

org.springframework.amqp.rabbit.listener.QueuesNotAvailableException: Cannot prepare queue for listener. Either the queue doesn't exist or the broker will not allow us to use it.
Copy the code

So I created the exchange and queue manually on the web side, and specified the routing key to bind the exchange and queue.

Now that the configuration part is done, it is time to write the code. For easy understanding, I have drawn a flow chart:

I will not explain the code, directly posted, compared to this flow chart should be able to understand. I also wrote the two little homework assignments that I mentioned in the video.

WeChatPayController:

/** * Payment result callback notification *@param request
 * @return
 * @throws Exception
 */
@RequestMapping("/notify/url")
public String notifyUrl(HttpServletRequest request) throws Exception {
    ServletInputStream inputStream = request.getInputStream();
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = 0;
    while((len = inputStream.read(buffer))! = -1) {
        outputStream.write(buffer,0,len);
    }
    String xmlString = outputStream.toString("UTF-8");

    // Convert a Java object into an AMQP message and send it
    rabbitTemplate.convertAndSend("exchange.order"."routing.order", xmlString);

    return  "
      
       
        
       
       
        
       
      ";
}
Copy the code

OrderMessageListener

@Component
@RabbitListener(queues = {"queue.order"})
public class OrderMessageListener {

    private final static String SUCCESS = "SUCCESS";

    @Autowired
    private OrderService orderService;

    @Autowired
    private WeChatPayFeign weChatPayFeign;

    /** * listen for messages from MQ *@paramMessage XML format */
    @RabbitHandler
    public void getMessage(String message) throws Exception {
        Map<String, String> map = WXPayUtil.xmlToMap(message);
        String returnCode = map.getOrDefault("return_code"."");     // Return the status code
        String resultCode = map.getOrDefault("result_code"."");     // Business results
        if (SUCCESS.equals(returnCode)) {   // Order status is changed after payment is successful
            String outTradeNo = map.get("out_trade_no");    // Merchant order number
            if (! SUCCESS.equals(resultCode)) { // Transaction failed, close order, change order status from database to payment failed, roll back inventory
                Map<String, String> closeResult = weChatPayFeign.closeOrder(outTradeNo).getData();  // The data returned by the server when the order is closed
                // If the error code is ORDERPAID, the order has been paid and will be processed as normal, otherwise the inventory will be rolled back
                if(! ("FAIL".equals(closeResult.get("result_code"&&))"ORDERPAID".equals(closeResult.get("err_code")))) {
                    orderService.deleteOrder(outTradeNo);
                    return;
                }
            }
            String transactionId = map.get("transaction_id");   // wechat Pay order number
            String timeEnd = map.get("time_end");               // Payment completion timeorderService.updateStatus(outTradeNo,timeEnd,transactionId); }}}Copy the code

OrderServiceImpl

@Override
public void updateStatus(String outTradeNo,String timeEnd,String transactionId) {
    Order order = orderMapper.findById(outTradeNo);
    LocalDateTime payTime = LocalDateTime.parse(timeEnd, DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));

    order.setPayStatus("1");                // The payment status is changed to 1 to indicate that the payment has been made
    order.setTransactionId(transactionId);  // Transaction serial number
    order.setPayTime(payTime);              // Transaction time

    orderMapper.updateByPrimaryKey(order);
}

@Override
public void deleteOrder(String outTradeNo) {
    Order order = orderMapper.findById(outTradeNo);
    LocalDateTime time = LocalDateTime.now();
    // Change the status
    order.setPayStatus("2");    // Transaction failed
    order.setUpdateTime(time);
    // Commit to the database
    orderMapper.updateByPrimaryKey(order);
    // Roll back inventory
    List<OrderItem> orderItems = orderItemMapper.findByOrderId(order.getId());
    List<Long> skuIds = new ArrayList<>();
    for (OrderItem orderItem : orderItems) {
        skuIds.add(orderItem.getSkuId());
    }
    List<Sku> skuList = skuFeign.findBySkuIds(order.getSkuIds()).getData(); // The corresponding SKU set in the database
    Map<Long, Sku> skuMap = skuList.stream().collect(Collectors.toMap(Sku::getId, a -> a));
    for (OrderItem orderItem : orderItems) {
        Sku sku = skuMap.get(orderItem.getSkuId());
        sku.setNum(sku.getNum()+orderItem.getNum());    / / add inventory
    }
    skuFeign.updateMap(skuMap);
}
Copy the code

Deal with order status regularly

When an order is created, the user may not pay, or we cannot get the result of payment due to network reasons. So we can use timed processing, which is to send a message to queue 1 of MQ after the order is placed, set expiration to 30 minutes, send the message to queue 2 after expiration, and then we listen to queue 2. In this way, once the message of queue 2 is monitored, it means that 30 minutes have passed before the order is placed. At this time, we go to check the order status. If it is paid, we ignore it; if it is not paid, we notify the wechat server to close the order and then delete the order from the database. To make it easier to understand, I drew a flow chart:

The code is pretty simple. First, we will add a configuration class to create the queue. It is OK to configure the queue directly on the Web page, but the previous section configured the queue on the Web page.

@Configuration
public class MqConfig {

    // Queue 1, delay queue, message expired to queue 2
    @Bean
    public Queue orderDelayQueue(a) {
        return QueueBuilder
                .durable("orderDelayQueue")
                //orderDelayQueue queue information will expire, after the expiration, it will enter the dead letter queue, the dead letter queue data bound to another switch
                .withArgument("x-dead-letter-exchange"."orderListenerExchange")
                .withArgument("x-dead-letter-routing-key"."orderListenerRoutingKey") // Bind the specified routing-key
                .build();
    }

    2 / / queue
    @Bean(name = "orderListenerQueue")  // The default name is the method name
    public Queue orderListenerQueue(a) {
        return new Queue("orderListenerQueue".true);
    }

    // Create a switch
    @Bean
    public Exchange orderListenerExchange(a) {
        return new DirectExchange("orderListenerExchange");
    }

    Queue2 is bound to Exchange
    @Bean
    public Binding orderListenerBinding(Queue orderListenerQueue,Exchange orderListenerExchange) {
        return BindingBuilder
                .bind(orderListenerQueue)
                .to(orderListenerExchange)
                .with("orderListenerRoutingKey") .noargs(); }}Copy the code

When queue 1 is created, the dead-letter queue data is configured to bind to the switch in queue 2 so that data will be sent to queue 2 when it expires.

The order number is then sent to queue 1 when the order is created:

//OrderServiceImpl
@Override
public synchronized void add(Order order) { order.setId(String.valueOf(idWorker.nextId())); ..................... rabbitTemplate.convertAndSend("orderDelayQueue", (Object)order.getId(), new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("30 * 60 * 1000");   // The timer expires after 30 minutes
            returnmessage; }}); }Copy the code

Listen on queue 2 and perform the corresponding operation

@Component
@RabbitListener(queues = "orderListenerQueue")
public class DelayMessageQueueListener {

    @Autowired
    private OrderService orderService;

    @Autowired
    private WeChatPayFeign weChatPayFeign;

    @RabbitHandler
    public void getMessage(String orderId) throws Exception {
        Order order = orderService.findById(orderId);
        if ("0".equals(order.getPayStatus())) {
            //0 indicates not paid, notifying wechat server to cancel the order, delete the order from the database, and roll back the inventory
            Map<String, String> closeResult = weChatPayFeign.closeOrder(orderId).getData();
            // If the error code is ORDERPAID, the order has been paid and will be processed as normal, otherwise the inventory will be rolled back
            if(! ("FAIL".equals(closeResult.get("result_code"&&))"ORDERPAID".equals(closeResult.get("err_code")))) { orderService.deleteOrder(orderId); }}}}Copy the code

OK, so that the order data can be processed regularly.

conclusion

This article is mainly to realize wechat scan code payment, because there are some problems with the SDK provided by wechat, it took some time, but it was finally completed. It then implements payment result callback notification and RabbitMQ listening, using Intranet penetration. Finally, two queues are used to process orders on time. The other problem is that I haven’t learned RabbitMQ systematically, so I am only half-way through a lot of the operations, referring to the video and then fumbling through the configuration of the Web page myself, I will learn RabbitMQ systematically later and then summarize the article.

If you think my writing is ok, please don’t be stingy with your praise!

Code: github.com/RobodLee/ch…

This article has included to my lot DayDayUP:github.com/RobodLee/DayDayUP warehouse, welcome Star